1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetProviderInfo;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.ContentProviderClient;
24 import android.content.ContentProviderOperation;
25 import android.content.ContentResolver;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.Intent.ShortcutIconResource;
30 import android.content.IntentFilter;
31 import android.content.SharedPreferences;
32 import android.content.pm.PackageManager;
33 import android.content.pm.ProviderInfo;
34 import android.content.pm.ResolveInfo;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.database.Cursor;
38 import android.graphics.Bitmap;
39 import android.graphics.Rect;
40 import android.net.Uri;
41 import android.os.Environment;
42 import android.os.Handler;
43 import android.os.HandlerThread;
44 import android.os.Parcelable;
45 import android.os.Process;
46 import android.os.RemoteException;
47 import android.os.SystemClock;
48 import android.provider.BaseColumns;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.util.LongSparseArray;
52 import android.util.Pair;
53
54 import com.android.launcher3.compat.AppWidgetManagerCompat;
55 import com.android.launcher3.compat.LauncherActivityInfoCompat;
56 import com.android.launcher3.compat.LauncherAppsCompat;
57 import com.android.launcher3.compat.PackageInstallerCompat;
58 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
59 import com.android.launcher3.compat.UserHandleCompat;
60 import com.android.launcher3.compat.UserManagerCompat;
61
62 import java.lang.ref.WeakReference;
63 import java.net.URISyntaxException;
64 import java.security.InvalidParameterException;
65 import java.text.Collator;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collection;
69 import java.util.Collections;
70 import java.util.Comparator;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Iterator;
74 import java.util.List;
75 import java.util.Map.Entry;
76 import java.util.Set;
77
78 /**
79 * Maintains in-memory state of the Launcher. It is expected that there should be only one
80 * LauncherModel object held in a static. Also provide APIs for updating the database state
81 * for the Launcher.
82 */
83 public class LauncherModel extends BroadcastReceiver
84 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
85 static final boolean DEBUG_LOADERS = false;
86 private static final boolean DEBUG_RECEIVER = false;
87 private static final boolean REMOVE_UNRESTORED_ICONS = true;
88 private static final boolean ADD_MANAGED_PROFILE_SHORTCUTS = false;
89
90 static final String TAG = "Launcher.Model";
91
92 public static final int LOADER_FLAG_NONE = 0;
93 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
94 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
95
96 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
97 private static final long INVALID_SCREEN_ID = -1L;
98
99 private final boolean mAppsCanBeOnRemoveableStorage;
100 private final boolean mOldContentProviderExists;
101
102 private final LauncherAppState mApp;
103 private final Object mLock = new Object();
104 private DeferredHandler mHandler = new DeferredHandler();
105 private LoaderTask mLoaderTask;
106 private boolean mIsLoaderTaskRunning;
107
108 /**
109 * Maintain a set of packages per user, for which we added a shortcut on the workspace.
110 */
111 private static final String INSTALLED_SHORTCUTS_SET_PREFIX = "installed_shortcuts_set_for_user_";
112
113 // Specific runnable types that are run on the main thread deferred handler, this allows us to
114 // clear all queued binding runnables when the Launcher activity is destroyed.
115 private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
116 private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
117
118 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
119
120 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
121 static {
122 sWorkerThread.start();
123 }
124 private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
125
126 // We start off with everything not loaded. After that, we assume that
127 // our monitoring of the package manager provides all updates and we never
128 // need to do a requery. These are only ever touched from the loader thread.
129 private boolean mWorkspaceLoaded;
130 private boolean mAllAppsLoaded;
131
132 // When we are loading pages synchronously, we can't just post the binding of items on the side
133 // pages as this delays the rotation process. Instead, we wait for a callback from the first
134 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
135 // a normal load, we also clear this set of Runnables.
136 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
137
138 private WeakReference<Callbacks> mCallbacks;
139
140 // < only access in worker thread >
141 AllAppsList mBgAllAppsList;
142
143 // The lock that must be acquired before referencing any static bg data structures. Unlike
144 // other locks, this one can generally be held long-term because we never expect any of these
145 // static data structures to be referenced outside of the worker thread except on the first
146 // load after configuration change.
147 static final Object sBgLock = new Object();
148
149 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
150 // LauncherModel to their ids
151 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
152
153 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
154 // created by LauncherModel that are directly on the home screen (however, no widgets or
155 // shortcuts within folders).
156 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
157
158 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
159 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
160 new ArrayList<LauncherAppWidgetInfo>();
161
162 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
163 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
164
165 // sBgWorkspaceScreens is the ordered set of workspace screens.
166 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
167
168 // sBgWidgetProviders is the set of widget providers including custom internal widgets
169 public static HashMap<ComponentName, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
170 public static boolean sWidgetProvidersDirty;
171
172 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
173 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
174 new HashMap<UserHandleCompat, HashSet<String>>();
175
176 // </ only access in worker thread >
177
178 private IconCache mIconCache;
179
180 protected int mPreviousConfigMcc;
181
182 private final LauncherAppsCompat mLauncherApps;
183 private final UserManagerCompat mUserManager;
184
185 public interface Callbacks {
186 public boolean setLoadOnResume();
187 public int getCurrentWorkspaceScreen();
188 public void startBinding();
189 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
190 boolean forceAnimateIcons);
191 public void bindScreens(ArrayList<Long> orderedScreenIds);
192 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
193 public void bindFolders(HashMap<Long,FolderInfo> folders);
194 public void finishBindingItems();
195 public void bindAppWidget(LauncherAppWidgetInfo info);
196 public void bindAllApplications(ArrayList<AppInfo> apps);
197 public void bindAppsAdded(ArrayList<Long> newScreens,
198 ArrayList<ItemInfo> addNotAnimated,
199 ArrayList<ItemInfo> addAnimated,
200 ArrayList<AppInfo> addedApps);
201 public void bindAppsUpdated(ArrayList<AppInfo> apps);
202 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
203 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
204 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
205 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo);
206 public void updatePackageBadge(String packageName);
207 public void bindComponentsRemoved(ArrayList<String> packageNames,
208 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
209 public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
210 public void bindSearchablesChanged();
211 public boolean isAllAppsButtonRank(int rank);
212 public void onPageBoundSynchronously(int page);
213 public void dumpLogsToLocalData();
214 public void bindAddPendingItem(PendingAddItemInfo info, long container, long screenId,
215 int[] cell, int spanX, int spanY);
216 }
217
218 public interface ItemInfoFilter {
219 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
220 }
221
222 public interface ScreenPosProvider {
223 int getScreenIndex(ArrayList<Long> screenIDs);
224 }
225
226 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
227 Context context = app.getContext();
228
229 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
230 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
231 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
232 // resource string.
233 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
234 ProviderInfo providerInfo =
235 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
236 ProviderInfo redirectProvider =
237 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
238
239 Log.d(TAG, "Old launcher provider: " + oldProvider);
240 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
241
242 if (mOldContentProviderExists) {
243 Log.d(TAG, "Old launcher provider exists.");
244 } else {
245 Log.d(TAG, "Old launcher provider does not exist.");
246 }
247
248 mApp = app;
249 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
250 mIconCache = iconCache;
251
252 final Resources res = context.getResources();
253 Configuration config = res.getConfiguration();
254 mPreviousConfigMcc = config.mcc;
255 mLauncherApps = LauncherAppsCompat.getInstance(context);
256 mUserManager = UserManagerCompat.getInstance(context);
257 }
258
259 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
260 * posted on the main thread handler. */
261 private void runOnMainThread(Runnable r) {
262 runOnMainThread(r, 0);
263 }
264 private void runOnMainThread(Runnable r, int type) {
265 if (sWorkerThread.getThreadId() == Process.myTid()) {
266 // If we are on the worker thread, post onto the main handler
267 mHandler.post(r);
268 } else {
269 r.run();
270 }
271 }
272
273 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
274 * posted on the worker thread handler. */
275 private static void runOnWorkerThread(Runnable r) {
276 if (sWorkerThread.getThreadId() == Process.myTid()) {
277 r.run();
278 } else {
279 // If we are not on the worker thread, then post to the worker handler
280 sWorker.post(r);
281 }
282 }
283
284 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
285 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
286 }
287
288 public void setPackageState(final ArrayList<PackageInstallInfo> installInfo) {
289 // Process the updated package state
290 Runnable r = new Runnable() {
291 public void run() {
292 Callbacks callbacks = getCallback();
293 if (callbacks != null) {
294 callbacks.updatePackageState(installInfo);
295 }
296 }
297 };
298 mHandler.post(r);
299 }
300
301 public void updatePackageBadge(final String packageName) {
302 // Process the updated package badge
303 Runnable r = new Runnable() {
304 public void run() {
305 Callbacks callbacks = getCallback();
306 if (callbacks != null) {
307 callbacks.updatePackageBadge(packageName);
308 }
309 }
310 };
311 mHandler.post(r);
312 }
313
314 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
315 final Callbacks callbacks = getCallback();
316
317 if (allAppsApps == null) {
318 throw new RuntimeException("allAppsApps must not be null");
319 }
320 if (allAppsApps.isEmpty()) {
321 return;
322 }
323
324 // Process the newly added applications and add them to the database first
325 Runnable r = new Runnable() {
326 public void run() {
327 runOnMainThread(new Runnable() {
328 public void run() {
329 Callbacks cb = getCallback();
330 if (callbacks == cb && cb != null) {
331 callbacks.bindAppsAdded(null, null, null, allAppsApps);
332 }
333 }
334 });
335 }
336 };
337 runOnWorkerThread(r);
338 }
339
340 public void addAndBindAddedWorkspaceApps(final Context context,
341 final ArrayList<ItemInfo> workspaceApps) {
342 addAndBindAddedWorkspaceApps(context, workspaceApps,
343 new ScreenPosProvider() {
344
345 @Override
346 public int getScreenIndex(ArrayList<Long> screenIDs) {
347 return screenIDs.isEmpty() ? 0 : 1;
348 }
349 }, 1, false);
350 }
351
352 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<Rect> occupiedPos,
353 int[] xy, int spanX, int spanY) {
354 LauncherAppState app = LauncherAppState.getInstance();
355 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
356 final int xCount = (int) grid.numColumns;
357 final int yCount = (int) grid.numRows;
358 boolean[][] occupied = new boolean[xCount][yCount];
359 if (occupiedPos != null) {
360 for (Rect r : occupiedPos) {
361 for (int x = r.left; 0 <= x && x < r.right && x < xCount; x++) {
362 for (int y = r.top; 0 <= y && y < r.bottom && y < yCount; y++) {
363 occupied[x][y] = true;
364 }
365 }
366 }
367 }
368 return CellLayout.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
369 }
370
371 /**
372 * Find a position on the screen for the given size or adds a new screen.
373 * @return screenId and the coordinates for the item.
374 */
375 private static Pair<Long, int[]> findSpaceForItem(
376 Context context,
377 ScreenPosProvider preferredScreen,
378 int fallbackStartScreen,
379 ArrayList<Long> workspaceScreens,
380 ArrayList<Long> addedWorkspaceScreensFinal,
381 int spanX, int spanY) {
382 // Load position of items which are on the desktop. We can't use sBgItemsIdMap because
383 // loadWorkspace() may not have been called.
384 final ContentResolver cr = context.getContentResolver();
385 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
386 new String[] {
387 LauncherSettings.Favorites.SCREEN,
388 LauncherSettings.Favorites.CELLX,
389 LauncherSettings.Favorites.CELLY,
390 LauncherSettings.Favorites.SPANX,
391 LauncherSettings.Favorites.SPANY,
392 LauncherSettings.Favorites.CONTAINER
393 },
394 "container=?",
395 new String[] { Integer.toString(LauncherSettings.Favorites.CONTAINER_DESKTOP) },
396 null);
397
398 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
399 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
400 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
401 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
402 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
403 LongSparseArray<ArrayList<Rect>> screenItems = new LongSparseArray<ArrayList<Rect>>();
404 try {
405 while (c.moveToNext()) {
406 Rect rect = new Rect();
407 rect.left = c.getInt(cellXIndex);
408 rect.top = c.getInt(cellYIndex);
409 rect.right = rect.left + Math.max(1, c.getInt(spanXIndex));
410 rect.bottom = rect.top + Math.max(1, c.getInt(spanYIndex));
411
412 long screenId = c.getInt(screenIndex);
413 ArrayList<Rect> items = screenItems.get(screenId);
414 if (items == null) {
415 items = new ArrayList<Rect>();
416 screenItems.put(screenId, items);
417 }
418 items.add(rect);
419 }
420 } catch (Exception e) {
421 screenItems.clear();
422 } finally {
423 c.close();
424 }
425
426 // Find appropriate space for the item.
427 long screenId = 0;
428 int[] cordinates = new int[2];
429 boolean found = false;
430
431 int screenCount = workspaceScreens.size();
432 // First check the preferred screen.
433 int preferredScreenIndex = preferredScreen.getScreenIndex(workspaceScreens);
434 if (preferredScreenIndex < screenCount) {
435 screenId = workspaceScreens.get(preferredScreenIndex);
436 found = findNextAvailableIconSpaceInScreen(
437 screenItems.get(screenId), cordinates, spanX, spanY);
438 }
439
440 if (!found) {
441 // Search on any of the screens.
442 for (int screen = fallbackStartScreen; screen < screenCount; screen++) {
443 screenId = workspaceScreens.get(screen);
444 if (findNextAvailableIconSpaceInScreen(
445 screenItems.get(screenId), cordinates, spanX, spanY)) {
446 // We found a space for it
447 found = true;
448 break;
449 }
450 }
451 }
452
453 if (!found) {
454 // Still no position found. Add a new screen to the end.
455 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
456
457 // Save the screen id for binding in the workspace
458 workspaceScreens.add(screenId);
459 addedWorkspaceScreensFinal.add(screenId);
460
461 // If we still can't find an empty space, then God help us all!!!
462 if (!findNextAvailableIconSpaceInScreen(
463 screenItems.get(screenId), cordinates, spanX, spanY)) {
464 throw new RuntimeException("Can't find space to add the item");
465 }
466 }
467 return Pair.create(screenId, cordinates);
468 }
469
470 /**
471 * Adds the provided items to the workspace.
472 * @param preferredScreen the screen where we should try to add the app first
473 * @param fallbackStartScreen the screen to start search for empty space if
474 * preferredScreen is not available.
475 */
476 public void addAndBindPendingItem(
477 final Context context,
478 final PendingAddItemInfo addInfo,
479 final ScreenPosProvider preferredScreen,
480 final int fallbackStartScreen) {
481 final Callbacks callbacks = getCallback();
482 // Process the newly added applications and add them to the database first
483 Runnable r = new Runnable() {
484 public void run() {
485 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
486 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
487
488 // Find appropriate space for the item.
489 Pair<Long, int[]> coords = findSpaceForItem(context, preferredScreen,
490 fallbackStartScreen, workspaceScreens, addedWorkspaceScreensFinal,
491 addInfo.spanX,
492 addInfo.spanY);
493 final long screenId = coords.first;
494 final int[] cordinates = coords.second;
495
496 // Update the workspace screens
497 updateWorkspaceScreenOrder(context, workspaceScreens);
498 runOnMainThread(new Runnable() {
499 public void run() {
500 Callbacks cb = getCallback();
501 if (callbacks == cb && cb != null) {
502 cb.bindAddScreens(addedWorkspaceScreensFinal);
503 cb.bindAddPendingItem(addInfo,
504 LauncherSettings.Favorites.CONTAINER_DESKTOP,
505 screenId, cordinates, addInfo.spanX, addInfo.spanY);
506 }
507 }
508 });
509 }
510 };
511 runOnWorkerThread(r);
512 }
513
514 /**
515 * Adds the provided items to the workspace.
516 * @param preferredScreen the screen where we should try to add the app first
517 * @param fallbackStartScreen the screen to start search for empty space if
518 * preferredScreen is not available.
519 */
520 public void addAndBindAddedWorkspaceApps(final Context context,
521 final ArrayList<ItemInfo> workspaceApps,
522 final ScreenPosProvider preferredScreen,
523 final int fallbackStartScreen,
524 final boolean allowDuplicate) {
525 final Callbacks callbacks = getCallback();
526 if (workspaceApps.isEmpty()) {
527 return;
528 }
529 // Process the newly added applications and add them to the database first
530 Runnable r = new Runnable() {
531 public void run() {
532 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
533 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
534
535 // Get the list of workspace screens. We need to append to this list and
536 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
537 // called.
538 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
539 synchronized(sBgLock) {
540 for (ItemInfo item : workspaceApps) {
541 if (!allowDuplicate) {
542 // Short-circuit this logic if the icon exists somewhere on the workspace
543 if (shortcutExists(context, item.title.toString(),
544 item.getIntent(), item.user)) {
545 continue;
546 }
547 }
548
549 // Find appropriate space for the item.
550 Pair<Long, int[]> coords = findSpaceForItem(context, preferredScreen,
551 fallbackStartScreen, workspaceScreens, addedWorkspaceScreensFinal,
552 1, 1);
553 long screenId = coords.first;
554 int[] cordinates = coords.second;
555
556 ShortcutInfo shortcutInfo;
557 if (item instanceof ShortcutInfo) {
558 shortcutInfo = (ShortcutInfo) item;
559 } else if (item instanceof AppInfo) {
560 shortcutInfo = ((AppInfo) item).makeShortcut();
561 } else {
562 throw new RuntimeException("Unexpected info type");
563 }
564
565 // Add the shortcut to the db
566 addItemToDatabase(context, shortcutInfo,
567 LauncherSettings.Favorites.CONTAINER_DESKTOP,
568 screenId, cordinates[0], cordinates[1], false);
569 // Save the ShortcutInfo for binding in the workspace
570 addedShortcutsFinal.add(shortcutInfo);
571 }
572 }
573
574 // Update the workspace screens
575 updateWorkspaceScreenOrder(context, workspaceScreens);
576
577 if (!addedShortcutsFinal.isEmpty()) {
578 runOnMainThread(new Runnable() {
579 public void run() {
580 Callbacks cb = getCallback();
581 if (callbacks == cb && cb != null) {
582 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
583 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
584 if (!addedShortcutsFinal.isEmpty()) {
585 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
586 long lastScreenId = info.screenId;
587 for (ItemInfo i : addedShortcutsFinal) {
588 if (i.screenId == lastScreenId) {
589 addAnimated.add(i);
590 } else {
591 addNotAnimated.add(i);
592 }
593 }
594 }
595 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
596 addNotAnimated, addAnimated, null);
597 }
598 }
599 });
600 }
601 }
602 };
603 runOnWorkerThread(r);
604 }
605
606 public void unbindItemInfosAndClearQueuedBindRunnables() {
607 if (sWorkerThread.getThreadId() == Process.myTid()) {
608 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
609 "main thread");
610 }
611
612 // Clear any deferred bind runnables
613 synchronized (mDeferredBindRunnables) {
614 mDeferredBindRunnables.clear();
615 }
616 // Remove any queued bind runnables
617 mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
618 // Unbind all the workspace items
619 unbindWorkspaceItemsOnMainThread();
620 }
621
622 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
623 void unbindWorkspaceItemsOnMainThread() {
624 // Ensure that we don't use the same workspace items data structure on the main thread
625 // by making a copy of workspace items first.
626 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
627 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
628 synchronized (sBgLock) {
629 tmpWorkspaceItems.addAll(sBgWorkspaceItems);
630 tmpAppWidgets.addAll(sBgAppWidgets);
631 }
632 Runnable r = new Runnable() {
633 @Override
634 public void run() {
635 for (ItemInfo item : tmpWorkspaceItems) {
636 item.unbind();
637 }
638 for (ItemInfo item : tmpAppWidgets) {
639 item.unbind();
640 }
641 }
642 };
643 runOnMainThread(r);
644 }
645
646 /**
647 * Adds an item to the DB if it was not created previously, or move it to a new
648 * <container, screen, cellX, cellY>
649 */
650 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
651 long screenId, int cellX, int cellY) {
652 if (item.container == ItemInfo.NO_ID) {
653 // From all apps
654 addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
655 } else {
656 // From somewhere else
657 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
658 }
659 }
660
661 static void checkItemInfoLocked(
662 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
663 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
664 if (modelItem != null && item != modelItem) {
665 // check all the data is consistent
666 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
667 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
668 ShortcutInfo shortcut = (ShortcutInfo) item;
669 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
670 modelShortcut.intent.filterEquals(shortcut.intent) &&
671 modelShortcut.id == shortcut.id &&
672 modelShortcut.itemType == shortcut.itemType &&
673 modelShortcut.container == shortcut.container &&
674 modelShortcut.screenId == shortcut.screenId &&
675 modelShortcut.cellX == shortcut.cellX &&
676 modelShortcut.cellY == shortcut.cellY &&
677 modelShortcut.spanX == shortcut.spanX &&
678 modelShortcut.spanY == shortcut.spanY &&
679 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
680 (modelShortcut.dropPos != null &&
681 shortcut.dropPos != null &&
682 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
683 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
684 // For all intents and purposes, this is the same object
685 return;
686 }
687 }
688
689 // the modelItem needs to match up perfectly with item if our model is
690 // to be consistent with the database-- for now, just require
691 // modelItem == item or the equality check above
692 String msg = "item: " + ((item != null) ? item.toString() : "null") +
693 "modelItem: " +
694 ((modelItem != null) ? modelItem.toString() : "null") +
695 "Error: ItemInfo passed to checkItemInfo doesn't match original";
696 RuntimeException e = new RuntimeException(msg);
697 if (stackTrace != null) {
698 e.setStackTrace(stackTrace);
699 }
700 throw e;
701 }
702 }
703
704 static void checkItemInfo(final ItemInfo item) {
705 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
706 final long itemId = item.id;
707 Runnable r = new Runnable() {
708 public void run() {
709 synchronized (sBgLock) {
710 checkItemInfoLocked(itemId, item, stackTrace);
711 }
712 }
713 };
714 runOnWorkerThread(r);
715 }
716
717 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
718 final ItemInfo item, final String callingFunction) {
719 final long itemId = item.id;
720 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
721 final ContentResolver cr = context.getContentResolver();
722
723 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
724 Runnable r = new Runnable() {
725 public void run() {
726 cr.update(uri, values, null, null);
727 updateItemArrays(item, itemId, stackTrace);
728 }
729 };
730 runOnWorkerThread(r);
731 }
732
733 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
734 final ArrayList<ItemInfo> items, final String callingFunction) {
735 final ContentResolver cr = context.getContentResolver();
736
737 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
738 Runnable r = new Runnable() {
739 public void run() {
740 ArrayList<ContentProviderOperation> ops =
741 new ArrayList<ContentProviderOperation>();
742 int count = items.size();
743 for (int i = 0; i < count; i++) {
744 ItemInfo item = items.get(i);
745 final long itemId = item.id;
746 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
747 ContentValues values = valuesList.get(i);
748
749 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
750 updateItemArrays(item, itemId, stackTrace);
751
752 }
753 try {
754 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
755 } catch (Exception e) {
756 e.printStackTrace();
757 }
758 }
759 };
760 runOnWorkerThread(r);
761 }
762
763 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
764 // Lock on mBgLock *after* the db operation
765 synchronized (sBgLock) {
766 checkItemInfoLocked(itemId, item, stackTrace);
767
768 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
769 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
770 // Item is in a folder, make sure this folder exists
771 if (!sBgFolders.containsKey(item.container)) {
772 // An items container is being set to a that of an item which is not in
773 // the list of Folders.
774 String msg = "item: " + item + " container being set to: " +
775 item.container + ", not in the list of folders";
776 Log.e(TAG, msg);
777 }
778 }
779
780 // Items are added/removed from the corresponding FolderInfo elsewhere, such
781 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
782 // that are on the desktop, as appropriate
783 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
784 if (modelItem != null &&
785 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
786 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
787 switch (modelItem.itemType) {
788 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
789 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
790 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
791 if (!sBgWorkspaceItems.contains(modelItem)) {
792 sBgWorkspaceItems.add(modelItem);
793 }
794 break;
795 default:
796 break;
797 }
798 } else {
799 sBgWorkspaceItems.remove(modelItem);
800 }
801 }
802 }
803
804 /**
805 * Move an item in the DB to a new <container, screen, cellX, cellY>
806 */
807 static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
808 final long screenId, final int cellX, final int cellY) {
809 item.container = container;
810 item.cellX = cellX;
811 item.cellY = cellY;
812
813 // We store hotseat items in canonical form which is this orientation invariant position
814 // in the hotseat
815 if (context instanceof Launcher && screenId < 0 &&
816 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
817 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
818 } else {
819 item.screenId = screenId;
820 }
821
822 final ContentValues values = new ContentValues();
823 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
824 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
825 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
826 values.put(LauncherSettings.Favorites.RANK, item.rank);
827 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
828
829 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
830 }
831
832 /**
833 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
834 * cellX, cellY have already been updated on the ItemInfos.
835 */
836 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
837 final long container, final int screen) {
838
839 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
840 int count = items.size();
841
842 for (int i = 0; i < count; i++) {
843 ItemInfo item = items.get(i);
844 item.container = container;
845
846 // We store hotseat items in canonical form which is this orientation invariant position
847 // in the hotseat
848 if (context instanceof Launcher && screen < 0 &&
849 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
850 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
851 item.cellY);
852 } else {
853 item.screenId = screen;
854 }
855
856 final ContentValues values = new ContentValues();
857 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
858 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
859 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
860 values.put(LauncherSettings.Favorites.RANK, item.rank);
861 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
862
863 contentValues.add(values);
864 }
865 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
866 }
867
868 /**
869 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
870 */
871 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
872 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
873 item.container = container;
874 item.cellX = cellX;
875 item.cellY = cellY;
876 item.spanX = spanX;
877 item.spanY = spanY;
878
879 // We store hotseat items in canonical form which is this orientation invariant position
880 // in the hotseat
881 if (context instanceof Launcher && screenId < 0 &&
882 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
883 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
884 } else {
885 item.screenId = screenId;
886 }
887
888 final ContentValues values = new ContentValues();
889 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
890 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
891 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
892 values.put(LauncherSettings.Favorites.RANK, item.rank);
893 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
894 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
895 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
896
897 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
898 }
899
900 /**
901 * Update an item to the database in a specified container.
902 */
903 static void updateItemInDatabase(Context context, final ItemInfo item) {
904 final ContentValues values = new ContentValues();
905 item.onAddToDatabase(context, values);
906 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
907 }
908
909 /**
910 * Returns true if the shortcuts already exists in the database.
911 * we identify a shortcut by its title and intent.
912 */
913 static boolean shortcutExists(Context context, String title, Intent intent,
914 UserHandleCompat user) {
915 final ContentResolver cr = context.getContentResolver();
916 final Intent intentWithPkg, intentWithoutPkg;
917
918 if (intent.getComponent() != null) {
919 // If component is not null, an intent with null package will produce
920 // the same result and should also be a match.
921 if (intent.getPackage() != null) {
922 intentWithPkg = intent;
923 intentWithoutPkg = new Intent(intent).setPackage(null);
924 } else {
925 intentWithPkg = new Intent(intent).setPackage(
926 intent.getComponent().getPackageName());
927 intentWithoutPkg = intent;
928 }
929 } else {
930 intentWithPkg = intent;
931 intentWithoutPkg = intent;
932 }
933 String userSerial = Long.toString(UserManagerCompat.getInstance(context)
934 .getSerialNumberForUser(user));
935 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
936 new String[] { "title", "intent", "profileId" },
937 "title=? and (intent=? or intent=?) and profileId=?",
938 new String[] { title, intentWithPkg.toUri(0), intentWithoutPkg.toUri(0), userSerial },
939 null);
940 try {
941 return c.moveToFirst();
942 } finally {
943 c.close();
944 }
945 }
946
947 /**
948 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
949 */
950 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
951 final ContentResolver cr = context.getContentResolver();
952 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
953 "_id=? and (itemType=? or itemType=?)",
954 new String[] { String.valueOf(id),
955 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
956
957 try {
958 if (c.moveToFirst()) {
959 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
960 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
961 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
962 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
963 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
964 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
965
966 FolderInfo folderInfo = null;
967 switch (c.getInt(itemTypeIndex)) {
968 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
969 folderInfo = findOrMakeFolder(folderList, id);
970 break;
971 }
972
973 folderInfo.title = c.getString(titleIndex);
974 folderInfo.id = id;
975 folderInfo.container = c.getInt(containerIndex);
976 folderInfo.screenId = c.getInt(screenIndex);
977 folderInfo.cellX = c.getInt(cellXIndex);
978 folderInfo.cellY = c.getInt(cellYIndex);
979
980 return folderInfo;
981 }
982 } finally {
983 c.close();
984 }
985
986 return null;
987 }
988
989 /**
990 * Add an item to the database in a specified container. Sets the container, screen, cellX and
991 * cellY fields of the item. Also assigns an ID to the item.
992 */
993 static void addItemToDatabase(Context context, final ItemInfo item, final long container,
994 final long screenId, final int cellX, final int cellY, final boolean notify) {
995 item.container = container;
996 item.cellX = cellX;
997 item.cellY = cellY;
998 // We store hotseat items in canonical form which is this orientation invariant position
999 // in the hotseat
1000 if (context instanceof Launcher && screenId < 0 &&
1001 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1002 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1003 } else {
1004 item.screenId = screenId;
1005 }
1006
1007 final ContentValues values = new ContentValues();
1008 final ContentResolver cr = context.getContentResolver();
1009 item.onAddToDatabase(context, values);
1010
1011 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1012 values.put(LauncherSettings.Favorites._ID, item.id);
1013
1014 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1015 Runnable r = new Runnable() {
1016 public void run() {
1017 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
1018 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
1019
1020 // Lock on mBgLock *after* the db operation
1021 synchronized (sBgLock) {
1022 checkItemInfoLocked(item.id, item, stackTrace);
1023 sBgItemsIdMap.put(item.id, item);
1024 switch (item.itemType) {
1025 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1026 sBgFolders.put(item.id, (FolderInfo) item);
1027 // Fall through
1028 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1029 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1030 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1031 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1032 sBgWorkspaceItems.add(item);
1033 } else {
1034 if (!sBgFolders.containsKey(item.container)) {
1035 // Adding an item to a folder that doesn't exist.
1036 String msg = "adding item: " + item + " to a folder that " +
1037 " doesn't exist";
1038 Log.e(TAG, msg);
1039 }
1040 }
1041 break;
1042 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1043 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1044 break;
1045 }
1046 }
1047 }
1048 };
1049 runOnWorkerThread(r);
1050 }
1051
1052 /**
1053 * Creates a new unique child id, for a given cell span across all layouts.
1054 */
1055 static int getCellLayoutChildId(
1056 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1057 return (((int) container & 0xFF) << 24)
1058 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1059 }
1060
1061 private static ArrayList<ItemInfo> getItemsByPackageName(
1062 final String pn, final UserHandleCompat user) {
1063 ItemInfoFilter filter = new ItemInfoFilter() {
1064 @Override
1065 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1066 return cn.getPackageName().equals(pn) && info.user.equals(user);
1067 }
1068 };
1069 return filterItemInfos(sBgItemsIdMap.values(), filter);
1070 }
1071
1072 /**
1073 * Removes all the items from the database corresponding to the specified package.
1074 */
1075 static void deletePackageFromDatabase(Context context, final String pn,
1076 final UserHandleCompat user) {
1077 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1078 }
1079
1080 /**
1081 * Removes the specified item from the database
1082 * @param context
1083 * @param item
1084 */
1085 static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1086 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1087 items.add(item);
1088 deleteItemsFromDatabase(context, items);
1089 }
1090
1091 /**
1092 * Removes the specified items from the database
1093 * @param context
1094 * @param item
1095 */
1096 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1097 final ContentResolver cr = context.getContentResolver();
1098
1099 Runnable r = new Runnable() {
1100 public void run() {
1101 for (ItemInfo item : items) {
1102 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id, false);
1103 cr.delete(uri, null, null);
1104
1105 // Lock on mBgLock *after* the db operation
1106 synchronized (sBgLock) {
1107 switch (item.itemType) {
1108 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1109 sBgFolders.remove(item.id);
1110 for (ItemInfo info: sBgItemsIdMap.values()) {
1111 if (info.container == item.id) {
1112 // We are deleting a folder which still contains items that
1113 // think they are contained by that folder.
1114 String msg = "deleting a folder (" + item + ") which still " +
1115 "contains items (" + info + ")";
1116 Log.e(TAG, msg);
1117 }
1118 }
1119 sBgWorkspaceItems.remove(item);
1120 break;
1121 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1122 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1123 sBgWorkspaceItems.remove(item);
1124 break;
1125 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1126 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1127 break;
1128 }
1129 sBgItemsIdMap.remove(item.id);
1130 }
1131 }
1132 }
1133 };
1134 runOnWorkerThread(r);
1135 }
1136
1137 /**
1138 * Update the order of the workspace screens in the database. The array list contains
1139 * a list of screen ids in the order that they should appear.
1140 */
1141 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1142 // Log to disk
1143 Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
1144 Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
1145
1146 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1147 final ContentResolver cr = context.getContentResolver();
1148 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1149
1150 // Remove any negative screen ids -- these aren't persisted
1151 Iterator<Long> iter = screensCopy.iterator();
1152 while (iter.hasNext()) {
1153 long id = iter.next();
1154 if (id < 0) {
1155 iter.remove();
1156 }
1157 }
1158
1159 Runnable r = new Runnable() {
1160 @Override
1161 public void run() {
1162 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1163 // Clear the table
1164 ops.add(ContentProviderOperation.newDelete(uri).build());
1165 int count = screensCopy.size();
1166 for (int i = 0; i < count; i++) {
1167 ContentValues v = new ContentValues();
1168 long screenId = screensCopy.get(i);
1169 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1170 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1171 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1172 }
1173
1174 try {
1175 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1176 } catch (Exception ex) {
1177 throw new RuntimeException(ex);
1178 }
1179
1180 synchronized (sBgLock) {
1181 sBgWorkspaceScreens.clear();
1182 sBgWorkspaceScreens.addAll(screensCopy);
1183 }
1184 }
1185 };
1186 runOnWorkerThread(r);
1187 }
1188
1189 /**
1190 * Remove the contents of the specified folder from the database
1191 */
1192 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1193 final ContentResolver cr = context.getContentResolver();
1194
1195 Runnable r = new Runnable() {
1196 public void run() {
1197 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1198 // Lock on mBgLock *after* the db operation
1199 synchronized (sBgLock) {
1200 sBgItemsIdMap.remove(info.id);
1201 sBgFolders.remove(info.id);
1202 sBgWorkspaceItems.remove(info);
1203 }
1204
1205 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
1206 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1207 // Lock on mBgLock *after* the db operation
1208 synchronized (sBgLock) {
1209 for (ItemInfo childInfo : info.contents) {
1210 sBgItemsIdMap.remove(childInfo.id);
1211 }
1212 }
1213 }
1214 };
1215 runOnWorkerThread(r);
1216 }
1217
1218 /**
1219 * Set this as the current Launcher activity object for the loader.
1220 */
1221 public void initialize(Callbacks callbacks) {
1222 synchronized (mLock) {
1223 mCallbacks = new WeakReference<Callbacks>(callbacks);
1224 }
1225 }
1226
1227 @Override
1228 public void onPackageChanged(String packageName, UserHandleCompat user) {
1229 int op = PackageUpdatedTask.OP_UPDATE;
1230 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1231 user));
1232 }
1233
1234 @Override
1235 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1236 int op = PackageUpdatedTask.OP_REMOVE;
1237 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1238 user));
1239 }
1240
1241 @Override
1242 public void onPackageAdded(String packageName, UserHandleCompat user) {
1243 int op = PackageUpdatedTask.OP_ADD;
1244 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1245 user));
1246 }
1247
1248 @Override
1249 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1250 boolean replacing) {
1251 if (!replacing) {
1252 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1253 user));
1254 if (mAppsCanBeOnRemoveableStorage) {
1255 // Only rebind if we support removable storage. It catches the
1256 // case where
1257 // apps on the external sd card need to be reloaded
1258 startLoaderFromBackground();
1259 }
1260 } else {
1261 // If we are replacing then just update the packages in the list
1262 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1263 packageNames, user));
1264 }
1265 }
1266
1267 @Override
1268 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1269 boolean replacing) {
1270 if (!replacing) {
1271 enqueuePackageUpdated(new PackageUpdatedTask(
1272 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1273 user));
1274 }
1275 }
1276
1277 /**
1278 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1279 * ACTION_PACKAGE_CHANGED.
1280 */
1281 @Override
1282 public void onReceive(Context context, Intent intent) {
1283 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1284
1285 final String action = intent.getAction();
1286 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1287 // If we have changed locale we need to clear out the labels in all apps/workspace.
1288 forceReload();
1289 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1290 // Check if configuration change was an mcc/mnc change which would affect app resources
1291 // and we would need to clear out the labels in all apps/workspace. Same handling as
1292 // above for ACTION_LOCALE_CHANGED
1293 Configuration currentConfig = context.getResources().getConfiguration();
1294 if (mPreviousConfigMcc != currentConfig.mcc) {
1295 Log.d(TAG, "Reload apps on config change. curr_mcc:"
1296 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
1297 forceReload();
1298 }
1299 // Update previousConfig
1300 mPreviousConfigMcc = currentConfig.mcc;
1301 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1302 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1303 Callbacks callbacks = getCallback();
1304 if (callbacks != null) {
1305 callbacks.bindSearchablesChanged();
1306 }
1307 }
1308 }
1309
1310 void forceReload() {
1311 resetLoadedState(true, true);
1312
1313 // Do this here because if the launcher activity is running it will be restarted.
1314 // If it's not running startLoaderFromBackground will merely tell it that it needs
1315 // to reload.
1316 startLoaderFromBackground();
1317 }
1318
1319 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1320 synchronized (mLock) {
1321 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1322 // mWorkspaceLoaded to true later
1323 stopLoaderLocked();
1324 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1325 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1326 }
1327 }
1328
1329 /**
1330 * When the launcher is in the background, it's possible for it to miss paired
1331 * configuration changes. So whenever we trigger the loader from the background
1332 * tell the launcher that it needs to re-run the loader when it comes back instead
1333 * of doing it now.
1334 */
1335 public void startLoaderFromBackground() {
1336 boolean runLoader = false;
1337 Callbacks callbacks = getCallback();
1338 if (callbacks != null) {
1339 // Only actually run the loader if they're not paused.
1340 if (!callbacks.setLoadOnResume()) {
1341 runLoader = true;
1342 }
1343 }
1344 if (runLoader) {
1345 startLoader(false, PagedView.INVALID_RESTORE_PAGE);
1346 }
1347 }
1348
1349 // If there is already a loader task running, tell it to stop.
1350 // returns true if isLaunching() was true on the old task
1351 private boolean stopLoaderLocked() {
1352 boolean isLaunching = false;
1353 LoaderTask oldTask = mLoaderTask;
1354 if (oldTask != null) {
1355 if (oldTask.isLaunching()) {
1356 isLaunching = true;
1357 }
1358 oldTask.stopLocked();
1359 }
1360 return isLaunching;
1361 }
1362
1363 public boolean isCurrentCallbacks(Callbacks callbacks) {
1364 return (mCallbacks != null && mCallbacks.get() == callbacks);
1365 }
1366
1367 public void startLoader(boolean isLaunching, int synchronousBindPage) {
1368 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
1369 }
1370
1371 public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
1372 synchronized (mLock) {
1373 if (DEBUG_LOADERS) {
1374 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
1375 }
1376
1377 // Clear any deferred bind-runnables from the synchronized load process
1378 // We must do this before any loading/binding is scheduled below.
1379 synchronized (mDeferredBindRunnables) {
1380 mDeferredBindRunnables.clear();
1381 }
1382
1383 // Don't bother to start the thread if we know it's not going to do anything
1384 if (mCallbacks != null && mCallbacks.get() != null) {
1385 // If there is already one running, tell it to stop.
1386 // also, don't downgrade isLaunching if we're already running
1387 isLaunching = isLaunching || stopLoaderLocked();
1388 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
1389 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1390 && mAllAppsLoaded && mWorkspaceLoaded) {
1391 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1392 } else {
1393 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1394 sWorker.post(mLoaderTask);
1395 }
1396 }
1397 }
1398 }
1399
1400 void bindRemainingSynchronousPages() {
1401 // Post the remaining side pages to be loaded
1402 if (!mDeferredBindRunnables.isEmpty()) {
1403 Runnable[] deferredBindRunnables = null;
1404 synchronized (mDeferredBindRunnables) {
1405 deferredBindRunnables = mDeferredBindRunnables.toArray(
1406 new Runnable[mDeferredBindRunnables.size()]);
1407 mDeferredBindRunnables.clear();
1408 }
1409 for (final Runnable r : deferredBindRunnables) {
1410 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
1411 }
1412 }
1413 }
1414
1415 public void stopLoader() {
1416 synchronized (mLock) {
1417 if (mLoaderTask != null) {
1418 mLoaderTask.stopLocked();
1419 }
1420 }
1421 }
1422
1423 /**
1424 * Loads the workspace screen ids in an ordered list.
1425 */
1426 private static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1427 final ContentResolver contentResolver = context.getContentResolver();
1428 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1429
1430 // Get screens ordered by rank.
1431 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1432 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1433 ArrayList<Long> screenIds = new ArrayList<Long>();
1434 try {
1435 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1436 while (sc.moveToNext()) {
1437 try {
1438 screenIds.add(sc.getLong(idIndex));
1439 } catch (Exception e) {
1440 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1441 + " - invalid screens: " + e, true);
1442 }
1443 }
1444 } finally {
1445 sc.close();
1446 }
1447 return screenIds;
1448 }
1449
1450 public boolean isAllAppsLoaded() {
1451 return mAllAppsLoaded;
1452 }
1453
1454 boolean isLoadingWorkspace() {
1455 synchronized (mLock) {
1456 if (mLoaderTask != null) {
1457 return mLoaderTask.isLoadingWorkspace();
1458 }
1459 }
1460 return false;
1461 }
1462
1463 /**
1464 * Runnable for the thread that loads the contents of the launcher:
1465 * - workspace icons
1466 * - widgets
1467 * - all apps icons
1468 */
1469 private class LoaderTask implements Runnable {
1470 private Context mContext;
1471 private boolean mIsLaunching;
1472 private boolean mIsLoadingAndBindingWorkspace;
1473 private boolean mStopped;
1474 private boolean mLoadAndBindStepFinished;
1475 private int mFlags;
1476
1477 LoaderTask(Context context, boolean isLaunching, int flags) {
1478 mContext = context;
1479 mIsLaunching = isLaunching;
1480 mFlags = flags;
1481 }
1482
1483 boolean isLaunching() {
1484 return mIsLaunching;
1485 }
1486
1487 boolean isLoadingWorkspace() {
1488 return mIsLoadingAndBindingWorkspace;
1489 }
1490
1491 private void loadAndBindWorkspace() {
1492 mIsLoadingAndBindingWorkspace = true;
1493
1494 // Load the workspace
1495 if (DEBUG_LOADERS) {
1496 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1497 }
1498
1499 if (!mWorkspaceLoaded) {
1500 loadWorkspace();
1501 synchronized (LoaderTask.this) {
1502 if (mStopped) {
1503 return;
1504 }
1505 mWorkspaceLoaded = true;
1506 }
1507 }
1508
1509 // Bind the workspace
1510 bindWorkspace(-1);
1511 }
1512
1513 private void waitForIdle() {
1514 // Wait until the either we're stopped or the other threads are done.
1515 // This way we don't start loading all apps until the workspace has settled
1516 // down.
1517 synchronized (LoaderTask.this) {
1518 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1519
1520 mHandler.postIdle(new Runnable() {
1521 public void run() {
1522 synchronized (LoaderTask.this) {
1523 mLoadAndBindStepFinished = true;
1524 if (DEBUG_LOADERS) {
1525 Log.d(TAG, "done with previous binding step");
1526 }
1527 LoaderTask.this.notify();
1528 }
1529 }
1530 });
1531
1532 while (!mStopped && !mLoadAndBindStepFinished) {
1533 try {
1534 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1535 // wait no longer than 1sec at a time
1536 this.wait(1000);
1537 } catch (InterruptedException ex) {
1538 // Ignore
1539 }
1540 }
1541 if (DEBUG_LOADERS) {
1542 Log.d(TAG, "waited "
1543 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1544 + "ms for previous step to finish binding");
1545 }
1546 }
1547 }
1548
1549 void runBindSynchronousPage(int synchronousBindPage) {
1550 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1551 // Ensure that we have a valid page index to load synchronously
1552 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1553 "valid page index");
1554 }
1555 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1556 // Ensure that we don't try and bind a specified page when the pages have not been
1557 // loaded already (we should load everything asynchronously in that case)
1558 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1559 }
1560 synchronized (mLock) {
1561 if (mIsLoaderTaskRunning) {
1562 // Ensure that we are never running the background loading at this point since
1563 // we also touch the background collections
1564 throw new RuntimeException("Error! Background loading is already running");
1565 }
1566 }
1567
1568 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1569 // data structures, we can't allow any other thread to touch that data, but because
1570 // this call is synchronous, we can get away with not locking).
1571
1572 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1573 // operations from the previous activity. We need to ensure that all queued operations
1574 // are executed before any synchronous binding work is done.
1575 mHandler.flush();
1576
1577 // Divide the set of loaded items into those that we are binding synchronously, and
1578 // everything else that is to be bound normally (asynchronously).
1579 bindWorkspace(synchronousBindPage);
1580 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1581 // arise from that.
1582 onlyBindAllApps();
1583 }
1584
1585 public void run() {
1586 synchronized (mLock) {
1587 mIsLoaderTaskRunning = true;
1588 }
1589 // Optimize for end-user experience: if the Launcher is up and // running with the
1590 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1591 // workspace first (default).
1592 keep_running: {
1593 // Elevate priority when Home launches for the first time to avoid
1594 // starving at boot time. Staring at a blank home is not cool.
1595 synchronized (mLock) {
1596 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
1597 (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
1598 android.os.Process.setThreadPriority(mIsLaunching
1599 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
1600 }
1601 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1602 loadAndBindWorkspace();
1603
1604 if (mStopped) {
1605 break keep_running;
1606 }
1607
1608 // Whew! Hard work done. Slow us down, and wait until the UI thread has
1609 // settled down.
1610 synchronized (mLock) {
1611 if (mIsLaunching) {
1612 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
1613 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1614 }
1615 }
1616 waitForIdle();
1617
1618 // second step
1619 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1620 loadAndBindAllApps();
1621
1622 // Restore the default thread priority after we are done loading items
1623 synchronized (mLock) {
1624 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1625 }
1626 }
1627
1628 <<<<<<< GitAnalyzerPlus_ours
1629 if (LauncherAppState.isDisableAllApps()) {
1630 // Ensure that all the applications that are in the system are
1631 // represented on the home screen.
1632 verifyApplications();
1633 ||||||| GitAnalyzerPlus_base
1634 // second step
1635 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1636 loadAndBindAllApps();
1637
1638 // Restore the default thread priority after we are done loading items
1639 synchronized (mLock) {
1640 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1641 }
1642 }
1643
1644 // Update the saved icons if necessary
1645 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1646 synchronized (sBgLock) {
1647 for (Object key : sBgDbIconCache.keySet()) {
1648 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1649 }
1650 sBgDbIconCache.clear();
1651 =======
1652 // Update the saved icons if necessary
1653 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1654 synchronized (sBgLock) {
1655 for (Object key : sBgDbIconCache.keySet()) {
1656 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1657 }
1658 sBgDbIconCache.clear();
1659 >>>>>>> GitAnalyzerPlus_theirs
1660 }
1661
1662 // Clear out this reference, otherwise we end up holding it until all of the
1663 // callback runnables are done.
1664 mContext = null;
1665
1666 synchronized (mLock) {
1667 // If we are still the last one to be scheduled, remove ourselves.
1668 if (mLoaderTask == this) {
1669 mLoaderTask = null;
1670 }
1671 mIsLoaderTaskRunning = false;
1672 }
1673 }
1674
1675 public void stopLocked() {
1676 synchronized (LoaderTask.this) {
1677 mStopped = true;
1678 this.notify();
1679 }
1680 }
1681
1682 /**
1683 * Gets the callbacks object. If we've been stopped, or if the launcher object
1684 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1685 * object that was around when the deferred message was scheduled, and if there's
1686 * a new Callbacks object around then also return null. This will save us from
1687 * calling onto it with data that will be ignored.
1688 */
1689 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1690 synchronized (mLock) {
1691 if (mStopped) {
1692 return null;
1693 }
1694
1695 if (mCallbacks == null) {
1696 return null;
1697 }
1698
1699 final Callbacks callbacks = mCallbacks.get();
1700 if (callbacks != oldCallbacks) {
1701 return null;
1702 }
1703 if (callbacks == null) {
1704 Log.w(TAG, "no mCallbacks");
1705 return null;
1706 }
1707
1708 return callbacks;
1709 }
1710 }
1711
1712 // check & update map of what's occupied; used to discard overlapping/invalid items
1713 private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) {
1714 LauncherAppState app = LauncherAppState.getInstance();
1715 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1716 final int countX = (int) grid.numColumns;
1717 final int countY = (int) grid.numRows;
1718
1719 long containerIndex = item.screenId;
1720 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1721 // Return early if we detect that an item is under the hotseat button
1722 if (mCallbacks == null ||
1723 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1724 Log.e(TAG, "Error loading shortcut into hotseat " + item
1725 + " into position (" + item.screenId + ":" + item.cellX + ","
1726 + item.cellY + ") occupied by all apps");
1727 return false;
1728 }
1729
1730 final ItemInfo[][] hotseatItems =
1731 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1732
1733 if (item.screenId >= grid.numHotseatIcons) {
1734 Log.e(TAG, "Error loading shortcut " + item
1735 + " into hotseat position " + item.screenId
1736 + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1)
1737 + ")");
1738 return false;
1739 }
1740
1741 if (hotseatItems != null) {
1742 if (hotseatItems[(int) item.screenId][0] != null) {
1743 Log.e(TAG, "Error loading shortcut into hotseat " + item
1744 + " into position (" + item.screenId + ":" + item.cellX + ","
1745 + item.cellY + ") occupied by "
1746 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1747 [(int) item.screenId][0]);
1748 return false;
1749 } else {
1750 hotseatItems[(int) item.screenId][0] = item;
1751 return true;
1752 }
1753 } else {
1754 final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
1755 items[(int) item.screenId][0] = item;
1756 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1757 return true;
1758 }
1759 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1760 // Skip further checking if it is not the hotseat or workspace container
1761 return true;
1762 }
1763
1764 if (!occupied.containsKey(item.screenId)) {
1765 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1766 occupied.put(item.screenId, items);
1767 }
1768
1769 final ItemInfo[][] screens = occupied.get(item.screenId);
1770 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1771 item.cellX < 0 || item.cellY < 0 ||
1772 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1773 Log.e(TAG, "Error loading shortcut " + item
1774 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1775 + item.cellX + "," + item.cellY
1776 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1777 return false;
1778 }
1779
1780 // Check if any workspace icons overlap with each other
1781 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1782 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1783 if (screens[x][y] != null) {
1784 Log.e(TAG, "Error loading shortcut " + item
1785 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1786 + x + "," + y
1787 + ") occupied by "
1788 + screens[x][y]);
1789 return false;
1790 }
1791 }
1792 }
1793 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1794 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1795 screens[x][y] = item;
1796 }
1797 }
1798
1799 return true;
1800 }
1801
1802 /** Clears all the sBg data structures */
1803 private void clearSBgDataStructures() {
1804 synchronized (sBgLock) {
1805 sBgWorkspaceItems.clear();
1806 sBgAppWidgets.clear();
1807 sBgFolders.clear();
1808 sBgItemsIdMap.clear();
1809 sBgWorkspaceScreens.clear();
1810 }
1811 }
1812
1813 private void loadWorkspace() {
1814 // Log to disk
1815 Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
1816
1817 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1818
1819 final Context context = mContext;
1820 final ContentResolver contentResolver = context.getContentResolver();
1821 final PackageManager manager = context.getPackageManager();
1822 final boolean isSafeMode = manager.isSafeMode();
1823 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1824 final boolean isSdCardReady = context.registerReceiver(null,
1825 new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
1826
1827 LauncherAppState app = LauncherAppState.getInstance();
1828 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1829 int countX = (int) grid.numColumns;
1830 int countY = (int) grid.numRows;
1831
1832 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1833 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1834 LauncherAppState.getLauncherProvider().deleteDatabase();
1835 }
1836
1837 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1838 // append the user's Launcher2 shortcuts
1839 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1840 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1841 } else {
1842 // Make sure the default workspace is loaded
1843 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1844 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1845 }
1846
1847 synchronized (sBgLock) {
1848 clearSBgDataStructures();
1849 final HashSet<String> installingPkgs = PackageInstallerCompat
1850 .getInstance(mContext).updateAndGetActiveSessionCache();
1851
1852 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1853 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1854 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION;
1855 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1856 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1857
1858 // +1 for the hotseat (it can be larger than the workspace)
1859 // Load workspace in reverse order to ensure that latest items are loaded first (and
1860 // before any earlier duplicates)
1861 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
1862
1863 try {
1864 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1865 final int intentIndex = c.getColumnIndexOrThrow
1866 (LauncherSettings.Favorites.INTENT);
1867 final int titleIndex = c.getColumnIndexOrThrow
1868 (LauncherSettings.Favorites.TITLE);
1869 final int iconTypeIndex = c.getColumnIndexOrThrow(
1870 LauncherSettings.Favorites.ICON_TYPE);
1871 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1872 final int iconPackageIndex = c.getColumnIndexOrThrow(
1873 LauncherSettings.Favorites.ICON_PACKAGE);
1874 final int iconResourceIndex = c.getColumnIndexOrThrow(
1875 LauncherSettings.Favorites.ICON_RESOURCE);
1876 final int containerIndex = c.getColumnIndexOrThrow(
1877 LauncherSettings.Favorites.CONTAINER);
1878 final int itemTypeIndex = c.getColumnIndexOrThrow(
1879 LauncherSettings.Favorites.ITEM_TYPE);
1880 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1881 LauncherSettings.Favorites.APPWIDGET_ID);
1882 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1883 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1884 final int screenIndex = c.getColumnIndexOrThrow(
1885 LauncherSettings.Favorites.SCREEN);
1886 final int cellXIndex = c.getColumnIndexOrThrow
1887 (LauncherSettings.Favorites.CELLX);
1888 final int cellYIndex = c.getColumnIndexOrThrow
1889 (LauncherSettings.Favorites.CELLY);
1890 final int spanXIndex = c.getColumnIndexOrThrow
1891 (LauncherSettings.Favorites.SPANX);
1892 final int spanYIndex = c.getColumnIndexOrThrow(
1893 LauncherSettings.Favorites.SPANY);
1894 final int rankIndex = c.getColumnIndexOrThrow(
1895 LauncherSettings.Favorites.RANK);
1896 final int restoredIndex = c.getColumnIndexOrThrow(
1897 LauncherSettings.Favorites.RESTORED);
1898 final int profileIdIndex = c.getColumnIndexOrThrow(
1899 LauncherSettings.Favorites.PROFILE_ID);
1900 //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1901 //final int displayModeIndex = c.getColumnIndexOrThrow(
1902 // LauncherSettings.Favorites.DISPLAY_MODE);
1903
1904 ShortcutInfo info;
1905 String intentDescription;
1906 LauncherAppWidgetInfo appWidgetInfo;
1907 int container;
1908 long id;
1909 Intent intent;
1910 UserHandleCompat user;
1911
1912 while (!mStopped && c.moveToNext()) {
1913 try {
1914 int itemType = c.getInt(itemTypeIndex);
1915 boolean restored = 0 != c.getInt(restoredIndex);
1916 boolean allowMissingTarget = false;
1917
1918 switch (itemType) {
1919 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1920 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1921 id = c.getLong(idIndex);
1922 intentDescription = c.getString(intentIndex);
1923 long serialNumber = c.getInt(profileIdIndex);
1924 user = mUserManager.getUserForSerialNumber(serialNumber);
1925 int promiseType = c.getInt(restoredIndex);
1926 int disabledState = 0;
1927 boolean itemReplaced = false;
1928 if (user == null) {
1929 // User has been deleted remove the item.
1930 itemsToRemove.add(id);
1931 continue;
1932 }
1933 try {
1934 intent = Intent.parseUri(intentDescription, 0);
1935 ComponentName cn = intent.getComponent();
1936 if (cn != null && cn.getPackageName() != null) {
1937 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1938 cn.getPackageName(), user);
1939 boolean validComponent = validPkg &&
1940 launcherApps.isActivityEnabledForProfile(cn, user);
1941
1942 if (validComponent) {
1943 if (restored) {
1944 // no special handling necessary for this item
1945 restoredRows.add(id);
1946 restored = false;
1947 }
1948 } else if (validPkg) {
1949 intent = null;
1950 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1951 // We allow auto install apps to have their intent
1952 // updated after an install.
1953 intent = manager.getLaunchIntentForPackage(
1954 cn.getPackageName());
1955 if (intent != null) {
1956 ContentValues values = new ContentValues();
1957 values.put(LauncherSettings.Favorites.INTENT,
1958 intent.toUri(0));
1959 updateItem(id, values);
1960 }
1961 }
1962
1963 if (intent == null) {
1964 // The app is installed but the component is no
1965 // longer available.
1966 Launcher.addDumpLog(TAG,
1967 "Invalid component removed: " + cn, true);
1968 itemsToRemove.add(id);
1969 continue;
1970 } else {
1971 // no special handling necessary for this item
1972 restoredRows.add(id);
1973 restored = false;
1974 }
1975 } else if (restored) {
1976 // Package is not yet available but might be
1977 // installed later.
1978 Launcher.addDumpLog(TAG,
1979 "package not yet restored: " + cn, true);
1980
1981 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1982 // Restore has started once.
1983 } else if (installingPkgs.contains(cn.getPackageName())) {
1984 // App restore has started. Update the flag
1985 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1986 ContentValues values = new ContentValues();
1987 values.put(LauncherSettings.Favorites.RESTORED,
1988 promiseType);
1989 updateItem(id, values);
1990 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE🔵
1991 // This is a common app. Try to replace this.
1992 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(🔵
1993 CommonAppTypeParser parser = new CommonAppTypeParser(id, 🔵
1994 if (parser.findDefaultApp()) {
1995 // Default app found. Replace it.
1996 intent = parser.parsedIntent;
1997 cn = intent.getComponent();
1998 ContentValues values = parser.parsedValues;
1999 values.put(LauncherSettings.Favorites.RESTORED, 0);
2000 updateItem(id, values);
2001 restored = false;
2002 itemReplaced = true;
2003
2004 } else if (REMOVE_UNRESTORED_ICONS) {
2005 Launcher.addDumpLog(TAG,
2006 "Unrestored package removed: " + cn, true);
2007 itemsToRemove.add(id);
2008 continue;
2009 }
2010 } else if (REMOVE_UNRESTORED_ICONS) {
2011 Launcher.addDumpLog(TAG,
2012 "Unrestored package removed: " + cn, true);
2013 itemsToRemove.add(id);
2014 continue;
2015 }
2016 } else if (launcherApps.isAppEnabled(
2017 manager, cn.getPackageName(),
2018 PackageManager.GET_UNINSTALLED_PACKAGES)) {
2019 // Package is present but not available.
2020 allowMissingTarget = true;
2021 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
2022 } else if (!isSdCardReady) {
2023 // SdCard is not ready yet. Package might get available,
2024 // once it is ready.
2025 Launcher.addDumpLog(TAG, "Invalid package: " + cn
2026 + " (check again later)", true);
2027 HashSet<String> pkgs = sPendingPackages.get(user);
2028 if (pkgs == null) {
2029 pkgs = new HashSet<String>();
2030 sPendingPackages.put(user, pkgs);
2031 }
2032 pkgs.add(cn.getPackageName());
2033 allowMissingTarget = true;
2034 // Add the icon on the workspace anyway.
2035
2036 } else {
2037 // Do not wait for external media load anymore.
2038 // Log the invalid package, and remove it
2039 Launcher.addDumpLog(TAG,
2040 "Invalid package removed: " + cn, true);
2041 itemsToRemove.add(id);
2042 continue;
2043 }
2044 } else if (cn == null) {
2045 // For shortcuts with no component, keep them as they are
2046 restoredRows.add(id);
2047 restored = false;
2048 }
2049 } catch (URISyntaxException e) {
2050 Launcher.addDumpLog(TAG,
2051 "Invalid uri: " + intentDescription, true);
2052 continue;
2053 }
2054
2055 if (itemReplaced) {
2056 if (user.equals(UserHandleCompat.myUserHandle())) {
2057 info = getAppShortcutInfo(manager, intent, user, context, null,
2058 iconIndex, titleIndex, false);
2059 } else {
2060 // Don't replace items for other profiles.
2061 itemsToRemove.add(id);
2062 continue;
2063 }
2064 } else if (restored) {
2065 if (user.equals(UserHandleCompat.myUserHandle())) {
2066 Launcher.addDumpLog(TAG,
2067 "constructing info for partially restored package",
2068 true);
2069 info = getRestoredItemInfo(c, titleIndex, intent, promiseType);
2070 intent = getRestoredItemIntent(c, context, intent);
2071 } else {
2072 // Don't restore items for other profiles.
2073 itemsToRemove.add(id);
2074 continue;
2075 }
2076 } else if (itemType ==
2077 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2078 info = getAppShortcutInfo(manager, intent, user, context, c,
2079 iconIndex, titleIndex, allowMissingTarget);
2080 } else {
2081 info = getShortcutInfo(c, context, iconTypeIndex,
2082 iconPackageIndex, iconResourceIndex, iconIndex,
2083 titleIndex);
2084
2085 // App shortcuts that used to be automatically added to Launcher
2086 // didn't always have the correct intent flags set, so do that
2087 // here
2088 if (intent.getAction() != null &&
2089 intent.getCategories() != null &&
2090 intent.getAction().equals(Intent.ACTION_MAIN) &&
2091 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2092 intent.addFlags(
2093 Intent.FLAG_ACTIVITY_NEW_TASK |
2094 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2095 }
2096 }
2097
2098 if (info != null) {
2099 info.id = id;
2100 info.intent = intent;
2101 container = c.getInt(containerIndex);
2102 info.container = container;
2103 info.screenId = c.getInt(screenIndex);
2104 info.cellX = c.getInt(cellXIndex);
2105 info.cellY = c.getInt(cellYIndex);
2106 info.rank = c.getInt(rankIndex);
2107 info.spanX = 1;
2108 info.spanY = 1;
2109 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2110 info.isDisabled = disabledState;
2111 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2112 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2113 }
2114
2115 // check & update map of what's occupied
2116 if (!checkItemPlacement(occupied, info)) {
2117 itemsToRemove.add(id);
2118 break;
2119 }
2120
2121 switch (container) {
2122 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2123 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2124 sBgWorkspaceItems.add(info);
2125 break;
2126 default:
2127 // Item is in a user folder
2128 FolderInfo folderInfo =
2129 findOrMakeFolder(sBgFolders, container);
2130 folderInfo.add(info);
2131 break;
2132 }
2133 sBgItemsIdMap.put(info.id, info);
2134 } else {
2135 throw new RuntimeException("Unexpected null ShortcutInfo");
2136 }
2137 break;
2138
2139 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2140 id = c.getLong(idIndex);
2141 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2142
2143 folderInfo.title = c.getString(titleIndex);
2144 folderInfo.id = id;
2145 container = c.getInt(containerIndex);
2146 folderInfo.container = container;
2147 folderInfo.screenId = c.getInt(screenIndex);
2148 folderInfo.cellX = c.getInt(cellXIndex);
2149 folderInfo.cellY = c.getInt(cellYIndex);
2150 folderInfo.spanX = 1;
2151 folderInfo.spanY = 1;
2152
2153 // check & update map of what's occupied
2154 if (!checkItemPlacement(occupied, folderInfo)) {
2155 itemsToRemove.add(id);
2156 break;
2157 }
2158
2159 switch (container) {
2160 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2161 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2162 sBgWorkspaceItems.add(folderInfo);
2163 break;
2164 }
2165
2166 if (restored) {
2167 // no special handling required for restored folders
2168 restoredRows.add(id);
2169 }
2170
2171 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2172 sBgFolders.put(folderInfo.id, folderInfo);
2173 break;
2174
2175 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2176 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2177 // Read all Launcher-specific widget details
2178 boolean customWidget = itemType ==
2179 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2180
2181 int appWidgetId = c.getInt(appWidgetIdIndex);
2182 String savedProvider = c.getString(appWidgetProviderIndex);
2183 id = c.getLong(idIndex);
2184 final ComponentName component =
2185 ComponentName.unflattenFromString(savedProvider);
2186
2187 final int restoreStatus = c.getInt(restoredIndex);
2188 final boolean isIdValid = (restoreStatus &
2189 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2190
2191 final boolean wasProviderReady = (restoreStatus &
2192 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2193
2194 final LauncherAppWidgetProviderInfo provider =
2195 LauncherModel.getProviderInfo(context,
2196 ComponentName.unflattenFromString(savedProvider));
2197
2198 final boolean isProviderReady = isValidProvider(provider);
2199 if (!isSafeMode && !customWidget &&
2200 wasProviderReady && !isProviderReady) {
2201 String log = "Deleting widget that isn't installed anymore: "
2202 + "id=" + id + " appWidgetId=" + appWidgetId;
2203
2204 Log.e(TAG, log);
2205 Launcher.addDumpLog(TAG, log, false);
2206 itemsToRemove.add(id);
2207 } else {
2208 if (isProviderReady) {
2209 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2210 provider.provider);
2211
2212 if (!customWidget) {
2213 int[] minSpan =
2214 Launcher.getMinSpanForWidget(context, provider);
2215 appWidgetInfo.minSpanX = minSpan[0];
2216 appWidgetInfo.minSpanY = minSpan[1];
2217 }
2218
2219 int status = restoreStatus;
2220 if (!wasProviderReady) {
2221 // If provider was not previously ready, update the
2222 // status and UI flag.
2223
2224 // Id would be valid only if the widget restore broadcast was🔵
2225 if (isIdValid) {
2226 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2227 } else {
2228 status &= ~LauncherAppWidgetInfo
2229 .FLAG_PROVIDER_NOT_READY;
2230 }
2231 }
2232 appWidgetInfo.restoreStatus = status;
2233 } else {
2234 Log.v(TAG, "Widget restore pending id=" + id
2235 + " appWidgetId=" + appWidgetId
2236 + " status =" + restoreStatus);
2237 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2238 component);
2239 appWidgetInfo.restoreStatus = restoreStatus;
2240
2241 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) 🔵
2242 // Restore has started once.
2243 } else if (installingPkgs.contains(component.getPackageName())) {
2244 // App restore has started. Update the flag
2245 appWidgetInfo.restoreStatus |=
2246 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2247 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2248 Launcher.addDumpLog(TAG,
2249 "Unrestored widget removed: " + component, true);
2250 itemsToRemove.add(id);
2251 continue;
2252 }
2253 }
2254
2255 appWidgetInfo.id = id;
2256 appWidgetInfo.screenId = c.getInt(screenIndex);
2257 appWidgetInfo.cellX = c.getInt(cellXIndex);
2258 appWidgetInfo.cellY = c.getInt(cellYIndex);
2259 appWidgetInfo.spanX = c.getInt(spanXIndex);
2260 appWidgetInfo.spanY = c.getInt(spanYIndex);
2261
2262 if (!customWidget) {
2263 int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
2264 appWidgetInfo.minSpanX = minSpan[0];
2265 appWidgetInfo.minSpanY = minSpan[1];
2266 }
2267
2268 container = c.getInt(containerIndex);
2269 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2270 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2271 Log.e(TAG, "Widget found where container != " +
2272 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2273 continue;
2274 }
2275
2276 appWidgetInfo.container = c.getInt(containerIndex);
2277 // check & update map of what's occupied
2278 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2279 itemsToRemove.add(id);
2280 break;
2281 }
2282
2283 if (!customWidget) {
2284 String providerName =
2285 appWidgetInfo.providerName.flattenToString();
2286 if (!providerName.equals(savedProvider) ||
2287 (appWidgetInfo.restoreStatus != restoreStatus)) {
2288 ContentValues values = new ContentValues();
2289 values.put(
2290 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2291 providerName);
2292 values.put(LauncherSettings.Favorites.RESTORED,
2293 appWidgetInfo.restoreStatus);
2294 updateItem(id, values);
2295 }
2296 }
2297 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2298 sBgAppWidgets.add(appWidgetInfo);
2299 }
2300 break;
2301 }
2302 } catch (Exception e) {
2303 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2304 }
2305 }
2306 } finally {
2307 if (c != null) {
2308 c.close();
2309 }
2310 }
2311
2312 // Break early if we've stopped loading
2313 if (mStopped) {
2314 clearSBgDataStructures();
2315 return;
2316 }
2317
2318 if (itemsToRemove.size() > 0) {
2319 ContentProviderClient client = contentResolver.acquireContentProviderClient(
2320 contentUri);
2321 // Remove dead items
2322 for (long id : itemsToRemove) {
2323 if (DEBUG_LOADERS) {
2324 Log.d(TAG, "Removed id = " + id);
2325 }
2326 // Don't notify content observers
2327 try {
2328 client.delete(LauncherSettings.Favorites.getContentUri(id, false),
2329 null, null);
2330 } catch (RemoteException e) {
2331 Log.w(TAG, "Could not remove id = " + id);
2332 }
2333 }
2334 }
2335
2336 if (restoredRows.size() > 0) {
2337 ContentProviderClient updater = contentResolver.acquireContentProviderClient(
2338 contentUri);
2339 // Update restored items that no longer require special handling
2340 try {
2341 StringBuilder selectionBuilder = new StringBuilder();
2342 selectionBuilder.append(LauncherSettings.Favorites._ID);
2343 selectionBuilder.append(" IN (");
2344 selectionBuilder.append(TextUtils.join(", ", restoredRows));
2345 selectionBuilder.append(")");
2346 ContentValues values = new ContentValues();
2347 values.put(LauncherSettings.Favorites.RESTORED, 0);
2348 updater.update(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
2349 values, selectionBuilder.toString(), null);
2350 } catch (RemoteException e) {
2351 Log.w(TAG, "Could not update restored rows");
2352 }
2353 }
2354
2355 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2356 context.registerReceiver(new AppsAvailabilityCheck(),
2357 new IntentFilter(StartupReceiver.SYSTEM_READY),
2358 null, sWorker);
2359 }
2360
2361 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2362 // Log to disk
2363 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
2364 TextUtils.join(", ", sBgWorkspaceScreens), true);
2365
2366 // Remove any empty screens
2367 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2368 for (ItemInfo item: sBgItemsIdMap.values()) {
2369 long screenId = item.screenId;
2370 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2371 unusedScreens.contains(screenId)) {
2372 unusedScreens.remove(screenId);
2373 }
2374 }
2375
2376 // If there are any empty screens remove them, and update.
2377 if (unusedScreens.size() != 0) {
2378 // Log to disk
2379 Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
2380 TextUtils.join(", ", unusedScreens), true);
2381
2382 sBgWorkspaceScreens.removeAll(unusedScreens);
2383 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2384 }
2385
2386 if (DEBUG_LOADERS) {
2387 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2388 Log.d(TAG, "workspace layout: ");
2389 int nScreens = occupied.size();
2390 for (int y = 0; y < countY; y++) {
2391 String line = "";
2392
2393 Iterator<Long> iter = occupied.keySet().iterator();
2394 while (iter.hasNext()) {
2395 long screenId = iter.next();
2396 if (screenId > 0) {
2397 line += " | ";
2398 }
2399 for (int x = 0; x < countX; x++) {
2400 ItemInfo[][] screen = occupied.get(screenId);
2401 if (x < screen.length && y < screen[x].length) {
2402 line += (screen[x][y] != null) ? "#" : ".";
2403 } else {
2404 line += "!";
2405 }
2406 }
2407 }
2408 Log.d(TAG, "[ " + line + " ]");
2409 }
2410 }
2411 }
2412 }
2413
2414 /**
2415 * Partially updates the item without any notification. Must be called on the worker thread.
2416 */
2417 private void updateItem(long itemId, ContentValues update) {
2418 mContext.getContentResolver().update(
2419 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
2420 update,
2421 BaseColumns._ID + "= ?",
2422 new String[]{Long.toString(itemId)});
2423 }
2424
2425 /** Filters the set of items who are directly or indirectly (via another container) on the
2426 * specified screen. */
2427 private void filterCurrentWorkspaceItems(long currentScreenId,
2428 ArrayList<ItemInfo> allWorkspaceItems,
2429 ArrayList<ItemInfo> currentScreenItems,
2430 ArrayList<ItemInfo> otherScreenItems) {
2431 // Purge any null ItemInfos
2432 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2433 while (iter.hasNext()) {
2434 ItemInfo i = iter.next();
2435 if (i == null) {
2436 iter.remove();
2437 }
2438 }
2439
2440 // Order the set of items by their containers first, this allows use to walk through the
2441 // list sequentially, build up a list of containers that are in the specified screen,
2442 // as well as all items in those containers.
2443 Set<Long> itemsOnScreen = new HashSet<Long>();
2444 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2445 @Override
2446 public int compare(ItemInfo lhs, ItemInfo rhs) {
2447 return (int) (lhs.container - rhs.container);
2448 }
2449 });
2450 for (ItemInfo info : allWorkspaceItems) {
2451 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2452 if (info.screenId == currentScreenId) {
2453 currentScreenItems.add(info);
2454 itemsOnScreen.add(info.id);
2455 } else {
2456 otherScreenItems.add(info);
2457 }
2458 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2459 currentScreenItems.add(info);
2460 itemsOnScreen.add(info.id);
2461 } else {
2462 if (itemsOnScreen.contains(info.container)) {
2463 currentScreenItems.add(info);
2464 itemsOnScreen.add(info.id);
2465 } else {
2466 otherScreenItems.add(info);
2467 }
2468 }
2469 }
2470 }
2471
2472 /** Filters the set of widgets which are on the specified screen. */
2473 private void filterCurrentAppWidgets(long currentScreenId,
2474 ArrayList<LauncherAppWidgetInfo> appWidgets,
2475 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2476 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2477
2478 for (LauncherAppWidgetInfo widget : appWidgets) {
2479 if (widget == null) continue;
2480 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2481 widget.screenId == currentScreenId) {
2482 currentScreenWidgets.add(widget);
2483 } else {
2484 otherScreenWidgets.add(widget);
2485 }
2486 }
2487 }
2488
2489 /** Filters the set of folders which are on the specified screen. */
2490 private void filterCurrentFolders(long currentScreenId,
2491 HashMap<Long, ItemInfo> itemsIdMap,
2492 HashMap<Long, FolderInfo> folders,
2493 HashMap<Long, FolderInfo> currentScreenFolders,
2494 HashMap<Long, FolderInfo> otherScreenFolders) {
2495
2496 for (long id : folders.keySet()) {
2497 ItemInfo info = itemsIdMap.get(id);
2498 FolderInfo folder = folders.get(id);
2499 if (info == null || folder == null) continue;
2500 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2501 info.screenId == currentScreenId) {
2502 currentScreenFolders.put(id, folder);
2503 } else {
2504 otherScreenFolders.put(id, folder);
2505 }
2506 }
2507 }
2508
2509 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2510 * right) */
2511 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2512 final LauncherAppState app = LauncherAppState.getInstance();
2513 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2514 // XXX: review this
2515 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2516 @Override
2517 public int compare(ItemInfo lhs, ItemInfo rhs) {
2518 int cellCountX = (int) grid.numColumns;
2519 int cellCountY = (int) grid.numRows;
2520 int screenOffset = cellCountX * cellCountY;
2521 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2522 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2523 lhs.cellY * cellCountX + lhs.cellX);
2524 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2525 rhs.cellY * cellCountX + rhs.cellX);
2526 return (int) (lr - rr);
2527 }
2528 });
2529 }
2530
2531 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2532 final ArrayList<Long> orderedScreens) {
2533 final Runnable r = new Runnable() {
2534 @Override
2535 public void run() {
2536 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2537 if (callbacks != null) {
2538 callbacks.bindScreens(orderedScreens);
2539 }
2540 }
2541 };
2542 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2543 }
2544
2545 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2546 final ArrayList<ItemInfo> workspaceItems,
2547 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2548 final HashMap<Long, FolderInfo> folders,
2549 ArrayList<Runnable> deferredBindRunnables) {
2550
2551 final boolean postOnMainThread = (deferredBindRunnables != null);
2552
2553 // Bind the workspace items
2554 int N = workspaceItems.size();
2555 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2556 final int start = i;
2557 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2558 final Runnable r = new Runnable() {
2559 @Override
2560 public void run() {
2561 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2562 if (callbacks != null) {
2563 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2564 false);
2565 }
2566 }
2567 };
2568 if (postOnMainThread) {
2569 synchronized (deferredBindRunnables) {
2570 deferredBindRunnables.add(r);
2571 }
2572 } else {
2573 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2574 }
2575 }
2576
2577 // Bind the folders
2578 if (!folders.isEmpty()) {
2579 final Runnable r = new Runnable() {
2580 public void run() {
2581 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2582 if (callbacks != null) {
2583 callbacks.bindFolders(folders);
2584 }
2585 }
2586 };
2587 if (postOnMainThread) {
2588 synchronized (deferredBindRunnables) {
2589 deferredBindRunnables.add(r);
2590 }
2591 } else {
2592 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2593 }
2594 }
2595
2596 // Bind the widgets, one at a time
2597 N = appWidgets.size();
2598 for (int i = 0; i < N; i++) {
2599 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2600 final Runnable r = new Runnable() {
2601 public void run() {
2602 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2603 if (callbacks != null) {
2604 callbacks.bindAppWidget(widget);
2605 }
2606 }
2607 };
2608 if (postOnMainThread) {
2609 deferredBindRunnables.add(r);
2610 } else {
2611 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2612 }
2613 }
2614 }
2615
2616 /**
2617 * Binds all loaded data to actual views on the main thread.
2618 */
2619 private void bindWorkspace(int synchronizeBindPage) {
2620 final long t = SystemClock.uptimeMillis();
2621 Runnable r;
2622
2623 // Don't use these two variables in any of the callback runnables.
2624 // Otherwise we hold a reference to them.
2625 final Callbacks oldCallbacks = mCallbacks.get();
2626 if (oldCallbacks == null) {
2627 // This launcher has exited and nobody bothered to tell us. Just bail.
2628 Log.w(TAG, "LoaderTask running with no launcher");
2629 return;
2630 }
2631
2632 // Save a copy of all the bg-thread collections
2633 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2634 ArrayList<LauncherAppWidgetInfo> appWidgets =
2635 new ArrayList<LauncherAppWidgetInfo>();
2636 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
2637 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
2638 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2639 synchronized (sBgLock) {
2640 workspaceItems.addAll(sBgWorkspaceItems);
2641 appWidgets.addAll(sBgAppWidgets);
2642 folders.putAll(sBgFolders);
2643 itemsIdMap.putAll(sBgItemsIdMap);
2644 orderedScreenIds.addAll(sBgWorkspaceScreens);
2645 }
2646
2647 final boolean isLoadingSynchronously =
2648 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2649 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2650 oldCallbacks.getCurrentWorkspaceScreen();
2651 if (currScreen >= orderedScreenIds.size()) {
2652 // There may be no workspace screens (just hotseat items and an empty page).
2653 currScreen = PagedView.INVALID_RESTORE_PAGE;
2654 }
2655 final int currentScreen = currScreen;
2656 final long currentScreenId = currentScreen < 0
2657 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2658
2659 // Load all the items that are on the current page first (and in the process, unbind
2660 // all the existing workspace items before we call startBinding() below.
2661 unbindWorkspaceItemsOnMainThread();
2662
2663 // Separate the items that are on the current screen, and all the other remaining items
2664 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2665 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2666 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2667 new ArrayList<LauncherAppWidgetInfo>();
2668 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2669 new ArrayList<LauncherAppWidgetInfo>();
2670 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
2671 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
2672
2673 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2674 otherWorkspaceItems);
2675 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2676 otherAppWidgets);
2677 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2678 otherFolders);
2679 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2680 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2681
2682 // Tell the workspace that we're about to start binding items
2683 r = new Runnable() {
2684 public void run() {
2685 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2686 if (callbacks != null) {
2687 callbacks.startBinding();
2688 }
2689 }
2690 };
2691 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2692
2693 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2694
2695 // Load items on the current page
2696 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2697 currentFolders, null);
2698 if (isLoadingSynchronously) {
2699 r = new Runnable() {
2700 public void run() {
2701 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2702 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2703 callbacks.onPageBoundSynchronously(currentScreen);
2704 }
2705 }
2706 };
2707 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2708 }
2709
2710 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2711 // work until after the first render)
2712 synchronized (mDeferredBindRunnables) {
2713 mDeferredBindRunnables.clear();
2714 }
2715 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2716 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2717
2718 // Tell the workspace that we're done binding items
2719 r = new Runnable() {
2720 public void run() {
2721 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2722 if (callbacks != null) {
2723 callbacks.finishBindingItems();
2724 }
2725
2726 // If we're profiling, ensure this is the last thing in the queue.
2727 if (DEBUG_LOADERS) {
2728 Log.d(TAG, "bound workspace in "
2729 + (SystemClock.uptimeMillis()-t) + "ms");
2730 }
2731
2732 mIsLoadingAndBindingWorkspace = false;
2733 }
2734 };
2735 if (isLoadingSynchronously) {
2736 synchronized (mDeferredBindRunnables) {
2737 mDeferredBindRunnables.add(r);
2738 }
2739 } else {
2740 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2741 }
2742 }
2743
2744 private void loadAndBindAllApps() {
2745 if (DEBUG_LOADERS) {
2746 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2747 }
2748 if (!mAllAppsLoaded) {
2749 loadAllApps();
2750 synchronized (LoaderTask.this) {
2751 if (mStopped) {
2752 return;
2753 }
2754 mAllAppsLoaded = true;
2755 }
2756 } else {
2757 onlyBindAllApps();
2758 }
2759 }
2760
2761 private void onlyBindAllApps() {
2762 final Callbacks oldCallbacks = mCallbacks.get();
2763 if (oldCallbacks == null) {
2764 // This launcher has exited and nobody bothered to tell us. Just bail.
2765 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2766 return;
2767 }
2768
2769 // shallow copy
2770 @SuppressWarnings("unchecked")
2771 final ArrayList<AppInfo> list
2772 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2773 Runnable r = new Runnable() {
2774 public void run() {
2775 final long t = SystemClock.uptimeMillis();
2776 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2777 if (callbacks != null) {
2778 callbacks.bindAllApplications(list);
2779 }
2780 if (DEBUG_LOADERS) {
2781 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2782 + (SystemClock.uptimeMillis()-t) + "ms");
2783 }
2784 }
2785 };
2786 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2787 if (isRunningOnMainThread) {
2788 r.run();
2789 } else {
2790 mHandler.post(r);
2791 }
2792 }
2793
2794 private void loadAllApps() {
2795 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2796
2797 final Callbacks oldCallbacks = mCallbacks.get();
2798 if (oldCallbacks == null) {
2799 // This launcher has exited and nobody bothered to tell us. Just bail.
2800 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2801 return;
2802 }
2803
2804 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
2805 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2806
2807 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2808
2809 // Clear the list of apps
2810 mBgAllAppsList.clear();
2811 SharedPreferences prefs = mContext.getSharedPreferences(
2812 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
2813 for (UserHandleCompat user : profiles) {
2814 // Query for the set of apps
2815 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2816 List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2817 if (DEBUG_LOADERS) {
2818 Log.d(TAG, "getActivityList took "
2819 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2820 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2821 }
2822 // Fail if we don't have any apps
2823 // TODO: Fix this. Only fail for the current user.
2824 if (apps == null || apps.isEmpty()) {
2825 return;
2826 }
2827
2828 // Update icon cache
2829 HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
2830
2831 // If any package icon has changed (app was updated while launcher was dead),
2832 // update the corresponding shortcuts.
2833 if (!updatedPackages.isEmpty()) {
2834 final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
2835 synchronized (sBgLock) {
2836 for (ItemInfo info : sBgItemsIdMap.values()) {
2837 if (info instanceof ShortcutInfo && user.equals(info.user)
2838 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION)🔵
2839 ShortcutInfo si = (ShortcutInfo) info;
2840 ComponentName cn = si.getTargetComponent();
2841 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2842 si.updateIcon(mIconCache);
2843 updates.add(si);
2844 }
2845 }
2846 }
2847 }
2848
2849 if (!updates.isEmpty()) {
2850 final UserHandleCompat userFinal = user;
2851 mHandler.post(new Runnable() {
2852
2853 public void run() {
2854 Callbacks cb = getCallback();
2855 if (cb != null) {
2856 cb.bindShortcutsChanged(
2857 updates, new ArrayList<ShortcutInfo>(), userFinal);
2858 }
2859 }
2860 });
2861 }
2862 }
2863
2864 // Create the ApplicationInfos
2865 for (int i = 0; i < apps.size(); i++) {
2866 LauncherActivityInfoCompat app = apps.get(i);
2867 // This builds the icon bitmaps.
2868 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2869 }
2870
2871 if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
2872 // Add shortcuts for packages which were installed while launcher was dead.
2873 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
2874 + mUserManager.getSerialNumberForUser(user);
2875 Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET🔵
2876 HashSet<String> newPackageSet = new HashSet<String>();
2877
2878 for (LauncherActivityInfoCompat info : apps) {
2879 String packageName = info.getComponentName().getPackageName();
2880 if (!packagesAdded.contains(packageName)
2881 && !newPackageSet.contains(packageName)) {
2882 InstallShortcutReceiver.queueInstallShortcut(info, mContext);
2883 }
2884 newPackageSet.add(packageName);
2885 }
2886
2887 prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
2888 }
2889 }
2890 // Huh? Shouldn't this be inside the Runnable below?
2891 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2892 mBgAllAppsList.added = new ArrayList<AppInfo>();
2893
2894 // Post callback on main thread
2895 mHandler.post(new Runnable() {
2896 public void run() {
2897 final long bindTime = SystemClock.uptimeMillis();
2898 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2899 if (callbacks != null) {
2900 callbacks.bindAllApplications(added);
2901 if (DEBUG_LOADERS) {
2902 Log.d(TAG, "bound " + added.size() + " apps in "
2903 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2904 }
2905 } else {
2906 Log.i(TAG, "not binding apps: no Launcher activity");
2907 }
2908 }
2909 });
2910
2911 if (DEBUG_LOADERS) {
2912 Log.d(TAG, "Icons processed in "
2913 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2914 }
2915 }
2916
2917 public void dumpState() {
2918 synchronized (sBgLock) {
2919 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2920 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
2921 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2922 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2923 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2924 }
2925 }
2926 }
2927
2928 void enqueuePackageUpdated(PackageUpdatedTask task) {
2929 sWorker.post(task);
2930 }
2931
2932 private class AppsAvailabilityCheck extends BroadcastReceiver {
2933
2934 @Override
2935 public void onReceive(Context context, Intent intent) {
2936 synchronized (sBgLock) {
2937 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2938 .getInstance(mApp.getContext());
2939 final PackageManager manager = context.getPackageManager();
2940 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2941 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2942 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2943 UserHandleCompat user = entry.getKey();
2944 packagesRemoved.clear();
2945 packagesUnavailable.clear();
2946 for (String pkg : entry.getValue()) {
2947 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2948 boolean packageOnSdcard = launcherApps.isAppEnabled(
2949 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
2950 if (packageOnSdcard) {
2951 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2952 packagesUnavailable.add(pkg);
2953 } else {
2954 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2955 packagesRemoved.add(pkg);
2956 }
2957 }
2958 }
2959 if (!packagesRemoved.isEmpty()) {
2960 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2961 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2962 }
2963 if (!packagesUnavailable.isEmpty()) {
2964 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2965 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
2966 }
2967 }
2968 sPendingPackages.clear();
2969 }
2970 }
2971 }
2972
2973 private class PackageUpdatedTask implements Runnable {
2974 int mOp;
2975 String[] mPackages;
2976 UserHandleCompat mUser;
2977
2978 public static final int OP_NONE = 0;
2979 public static final int OP_ADD = 1;
2980 public static final int OP_UPDATE = 2;
2981 public static final int OP_REMOVE = 3; // uninstlled
2982 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2983
2984
2985 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2986 mOp = op;
2987 mPackages = packages;
2988 mUser = user;
2989 }
2990
2991 public void run() {
2992 final Context context = mApp.getContext();
2993
2994 final String[] packages = mPackages;
2995 final int N = packages.length;
2996 switch (mOp) {
2997 case OP_ADD:
2998 for (int i=0; i<N; i++) {
2999 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
3000 mIconCache.updateIconsForPkg(packages[i], mUser);
3001 mBgAllAppsList.addPackage(context, packages[i], mUser);
3002 }
3003
3004 // Auto add shortcuts for added packages.
3005 if (ADD_MANAGED_PROFILE_SHORTCUTS
3006 && !UserHandleCompat.myUserHandle().equals(mUser)) {
3007 SharedPreferences prefs = context.getSharedPreferences(
3008 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
3009 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
3010 + mUserManager.getSerialNumberForUser(mUser);
3011 Set<String> shortcutSet = new HashSet<String>(
3012 prefs.getStringSet(shortcutsSetKey,Collections.EMPTY_SET));
3013
3014 for (int i=0; i<N; i++) {
3015 if (!shortcutSet.contains(packages[i])) {
3016 shortcutSet.add(packages[i]);
3017 List<LauncherActivityInfoCompat> activities =
3018 mLauncherApps.getActivityList(packages[i], mUser);
3019 if (activities != null && !activities.isEmpty()) {
3020 InstallShortcutReceiver.queueInstallShortcut(
3021 activities.get(0), context);
3022 }
3023 }
3024 }
3025
3026 prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
3027 }
3028 break;
3029 case OP_UPDATE:
3030 for (int i=0; i<N; i++) {
3031 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3032 mIconCache.updateIconsForPkg(packages[i], mUser);
3033 mBgAllAppsList.updatePackage(context, packages[i], mUser);
3034 WidgetPreviewLoader.removePackageFromDb(
3035 mApp.getWidgetPreviewCacheDb(), packages[i]);
3036 }
3037 break;
3038 case OP_REMOVE:
3039 // Remove the packageName for the set of auto-installed shortcuts. This
3040 // will ensure that the shortcut when the app is installed again.
3041 if (ADD_MANAGED_PROFILE_SHORTCUTS
3042 && !UserHandleCompat.myUserHandle().equals(mUser)) {
3043 SharedPreferences prefs = context.getSharedPreferences(
3044 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
3045 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
3046 + mUserManager.getSerialNumberForUser(mUser);
3047 HashSet<String> shortcutSet = new HashSet<String>(
3048 prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET));
3049 shortcutSet.removeAll(Arrays.asList(mPackages));
3050 prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
3051 }
3052 for (int i=0; i<N; i++) {
3053 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3054 mIconCache.removeIconsForPkg(packages[i], mUser);
3055 }
3056 // Fall through
3057 case OP_UNAVAILABLE:
3058 for (int i=0; i<N; i++) {
3059 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3060 mBgAllAppsList.removePackage(packages[i], mUser);
3061 WidgetPreviewLoader.removePackageFromDb(
3062 mApp.getWidgetPreviewCacheDb(), packages[i]);
3063 }
3064 break;
3065 }
3066
3067 ArrayList<AppInfo> added = null;
3068 ArrayList<AppInfo> modified = null;
3069 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3070
3071 if (mBgAllAppsList.added.size() > 0) {
3072 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
3073 mBgAllAppsList.added.clear();
3074 }
3075 if (mBgAllAppsList.modified.size() > 0) {
3076 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
3077 mBgAllAppsList.modified.clear();
3078 }
3079 if (mBgAllAppsList.removed.size() > 0) {
3080 removedApps.addAll(mBgAllAppsList.removed);
3081 mBgAllAppsList.removed.clear();
3082 }
3083
3084 final Callbacks callbacks = getCallback();
3085 if (callbacks == null) {
3086 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
3087 return;
3088 }
3089
3090 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
3091 new HashMap<ComponentName, AppInfo>();
3092
3093 if (added != null) {
3094 addAppsToAllApps(context, added);
3095 for (AppInfo ai : added) {
3096 addedOrUpdatedApps.put(ai.componentName, ai);
3097 }
3098 }
3099
3100 if (modified != null) {
3101 final ArrayList<AppInfo> modifiedFinal = modified;
3102 for (AppInfo ai : modified) {
3103 addedOrUpdatedApps.put(ai.componentName, ai);
3104 }
3105
3106 mHandler.post(new Runnable() {
3107 public void run() {
3108 Callbacks cb = getCallback();
3109 if (callbacks == cb && cb != null) {
3110 callbacks.bindAppsUpdated(modifiedFinal);
3111 }
3112 }
3113 });
3114 }
3115
3116 // Update shortcut infos
3117 if (mOp == OP_ADD || mOp == OP_UPDATE) {
3118 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3119 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3120 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3121
3122 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
3123 synchronized (sBgLock) {
3124 for (ItemInfo info : sBgItemsIdMap.values()) {
3125 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3126 ShortcutInfo si = (ShortcutInfo) info;
3127 boolean infoUpdated = false;
3128 boolean shortcutUpdated = false;
3129
3130 // Update shortcuts which use iconResource.
3131 if ((si.iconResource != null)
3132 && packageSet.contains(si.iconResource.packageName)) {
3133 Bitmap icon = Utilities.createIconBitmap(si.iconResource.packageName,
3134 si.iconResource.resourceName, mIconCache, context);
3135 if (icon != null) {
3136 si.setIcon(icon);
3137 si.usingFallbackIcon = false;
3138 infoUpdated = true;
3139 }
3140 }
3141
3142 ComponentName cn = si.getTargetComponent();
3143 if (cn != null && packageSet.contains(cn.getPackageName())) {
3144 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3145
3146 if (si.isPromise()) {
3147 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3148 // Auto install icon
3149 PackageManager pm = context.getPackageManager();
3150 ResolveInfo matched = pm.resolveActivity(
3151 new Intent(Intent.ACTION_MAIN)
3152 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3153 PackageManager.MATCH_DEFAULT_ONLY);
3154 if (matched == null) {
3155 // Try to find the best match activity.
3156 Intent intent = pm.getLaunchIntentForPackage(
3157 cn.getPackageName());
3158 if (intent != null) {
3159 cn = intent.getComponent();
3160 appInfo = addedOrUpdatedApps.get(cn);
3161 }
3162
3163 if ((intent == null) || (appInfo == null)) {
3164 removedShortcuts.add(si);
3165 continue;
3166 }
3167 si.promisedIntent = intent;
3168 }
3169 }
3170
3171 // Restore the shortcut.
3172 si.intent = si.promisedIntent;
3173 si.promisedIntent = null;
3174 si.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
3175 & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
3176 & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
3177
3178 infoUpdated = true;
3179 si.updateIcon(mIconCache);
3180 }
3181
3182 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3183 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATIO🔵
3184 si.updateIcon(mIconCache);
3185 si.title = appInfo.title.toString();
3186 si.contentDescription = appInfo.contentDescription;
3187 infoUpdated = true;
3188 }
3189
3190 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3191 // Since package was just updated, the target must be available now.
3192 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3193 shortcutUpdated = true;
3194 }
3195 }
3196
3197 if (infoUpdated || shortcutUpdated) {
3198 updatedShortcuts.add(si);
3199 }
3200 if (infoUpdated) {
3201 updateItemInDatabase(context, si);
3202 }
3203 } else if (info instanceof LauncherAppWidgetInfo) {
3204 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3205 if (mUser.equals(widgetInfo.user)
3206 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_🔵
3207 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
3208 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3209 widgets.add(widgetInfo);
3210 updateItemInDatabase(context, widgetInfo);
3211 }
3212 }
3213 }
3214 }
3215
3216 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
3217 mHandler.post(new Runnable() {
3218
3219 public void run() {
3220 Callbacks cb = getCallback();
3221 if (callbacks == cb && cb != null) {
3222 callbacks.bindShortcutsChanged(
3223 updatedShortcuts, removedShortcuts, mUser);
3224 }
3225 }
3226 });
3227 if (!removedShortcuts.isEmpty()) {
3228 deleteItemsFromDatabase(context, removedShortcuts);
3229 }
3230 }
3231 if (!widgets.isEmpty()) {
3232 mHandler.post(new Runnable() {
3233 public void run() {
3234 Callbacks cb = getCallback();
3235 if (callbacks == cb && cb != null) {
3236 callbacks.bindWidgetsRestored(widgets);
3237 }
3238 }
3239 });
3240 }
3241 }
3242
3243 final ArrayList<String> removedPackageNames =
3244 new ArrayList<String>();
3245 if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
3246 // Mark all packages in the broadcast to be removed
3247 removedPackageNames.addAll(Arrays.asList(packages));
3248 } else if (mOp == OP_UPDATE) {
3249 // Mark disabled packages in the broadcast to be removed
3250 for (int i=0; i<N; i++) {
3251 if (isPackageDisabled(context, packages[i], mUser)) {
3252 removedPackageNames.add(packages[i]);
3253 }
3254 }
3255 }
3256
3257 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3258 final int removeReason;
3259 if (mOp == OP_UNAVAILABLE) {
3260 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3261 } else {
3262 // Remove all the components associated with this package
3263 for (String pn : removedPackageNames) {
3264 deletePackageFromDatabase(context, pn, mUser);
3265 }
3266 // Remove all the specific components
3267 for (AppInfo a : removedApps) {
3268 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3269 deleteItemsFromDatabase(context, infos);
3270 }
3271 removeReason = 0;
3272 }
3273
3274 // Remove any queued items from the install queue
3275 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3276 // Call the components-removed callback
3277 mHandler.post(new Runnable() {
3278 public void run() {
3279 Callbacks cb = getCallback();
3280 if (callbacks == cb && cb != null) {
3281 callbacks.bindComponentsRemoved(
3282 removedPackageNames, removedApps, mUser, removeReason);
3283 }
3284 }
3285 });
3286 }
3287
3288 final ArrayList<Object> widgetsAndShortcuts =
3289 getSortedWidgetsAndShortcuts(context);
3290 mHandler.post(new Runnable() {
3291 @Override
3292 public void run() {
3293 Callbacks cb = getCallback();
3294 if (callbacks == cb && cb != null) {
3295 callbacks.bindPackagesUpdated(widgetsAndShortcuts);
3296 }
3297 }
3298 });
3299
3300 // Write all the logs to disk
3301 mHandler.post(new Runnable() {
3302 public void run() {
3303 Callbacks cb = getCallback();
3304 if (callbacks == cb && cb != null) {
3305 callbacks.dumpLogsToLocalData();
3306 }
3307 }
3308 });
3309 }
3310 }
3311
3312 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context) {
3313 synchronized (sBgLock) {
3314 if (sBgWidgetProviders != null && !sWidgetProvidersDirty) {
3315 return new ArrayList<LauncherAppWidgetProviderInfo>(sBgWidgetProviders.values());
3316 }
3317 sBgWidgetProviders = new HashMap<ComponentName, LauncherAppWidgetProviderInfo>();
3318 List<AppWidgetProviderInfo> widgets =
3319 AppWidgetManagerCompat.getInstance(context).getAllProviders();
3320 LauncherAppWidgetProviderInfo info;
3321 for (AppWidgetProviderInfo pInfo : widgets) {
3322 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3323 sBgWidgetProviders.put(info.provider, info);
3324 }
3325
3326 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3327 for (CustomAppWidget widget : customWidgets) {
3328 info = new LauncherAppWidgetProviderInfo(context, widget);
3329 sBgWidgetProviders.put(info.provider, info);
3330 }
3331 sWidgetProvidersDirty = false;
3332 return new ArrayList<LauncherAppWidgetProviderInfo>(sBgWidgetProviders.values());
3333 }
3334 }
3335
3336 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name) {
3337 synchronized (sBgLock) {
3338 if (sBgWidgetProviders == null) {
3339 getWidgetProviders(ctx);
3340 }
3341 return sBgWidgetProviders.get(name);
3342 }
3343 }
3344
3345 // Returns a list of ResolveInfos/AppWindowInfos in sorted order
3346 public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
3347 PackageManager packageManager = context.getPackageManager();
3348 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3349 widgetsAndShortcuts.addAll(getWidgetProviders(context));
3350 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3351 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3352 Collections.sort(widgetsAndShortcuts, new WidgetAndShortcutNameComparator(context));
3353 return widgetsAndShortcuts;
3354 }
3355
3356 private static boolean isPackageDisabled(Context context, String packageName,
3357 UserHandleCompat user) {
3358 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3359 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3360 }
3361
3362 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3363 UserHandleCompat user) {
3364 if (cn == null) {
3365 return false;
3366 }
3367 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3368 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3369 return false;
3370 }
3371 return launcherApps.isActivityEnabledForProfile(cn, user);
3372 }
3373
3374 public static boolean isValidPackage(Context context, String packageName,
3375 UserHandleCompat user) {
3376 if (packageName == null) {
3377 return false;
3378 }
3379 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3380 return launcherApps.isPackageEnabledForProfile(packageName, user);
3381 }
3382
3383 /**
3384 * Make an ShortcutInfo object for a restored application or shortcut item that points
3385 * to a package that is not yet installed on the system.
3386 */
3387 public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent,
3388 int promiseType) {
3389 final ShortcutInfo info = new ShortcutInfo();
3390 info.user = UserHandleCompat.myUserHandle();
3391 mIconCache.getTitleAndIcon(info, intent, info.user);
3392
3393 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3394 String title = (cursor != null) ? cursor.getString(titleIndex) : null;
3395 if (!TextUtils.isEmpty(title)) {
3396 info.title = title;
3397 }
3398 info.status = ShortcutInfo.FLAG_RESTORED_ICON;
3399 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3400 if (TextUtils.isEmpty(info.title)) {
3401 info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
3402 }
3403 info.status = ShortcutInfo.FLAG_AUTOINTALL_ICON;
3404 } else {
3405 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3406 }
3407
3408 info.contentDescription = mUserManager.getBadgedLabelForUser(
3409 info.title.toString(), info.user);
3410 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3411 info.promisedIntent = intent;
3412 return info;
3413 }
3414
3415 /**
3416 * Make an Intent object for a restored application or shortcut item that points
3417 * to the market page for the item.
3418 */
3419 private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3420 ComponentName componentName = intent.getComponent();
3421 return getMarketIntent(componentName.getPackageName());
3422 }
3423
3424 static Intent getMarketIntent(String packageName) {
3425 return new Intent(Intent.ACTION_VIEW)
3426 .setData(new Uri.Builder()
3427 .scheme("market")
3428 .authority("details")
3429 .appendQueryParameter("id", packageName)
3430 .build());
3431 }
3432
3433 /**
3434 * Make an ShortcutInfo object for a shortcut that is an application.
3435 *
3436 * If c is not null, then it will be used to fill in missing data like the title and icon.
3437 */
3438 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
3439 UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
3440 boolean allowMissingTarget) {
3441 if (user == null) {
3442 Log.d(TAG, "Null user found in getShortcutInfo");
3443 return null;
3444 }
3445
3446 ComponentName componentName = intent.getComponent();
3447 if (componentName == null) {
3448 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3449 return null;
3450 }
3451
3452 Intent newIntent = new Intent(intent.getAction(), null);
3453 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3454 newIntent.setComponent(componentName);
3455 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3456 if ((lai == null) && !allowMissingTarget) {
3457 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3458 return null;
3459 }
3460
3461 final ShortcutInfo info = new ShortcutInfo();
3462 mIconCache.getTitleAndIcon(info, componentName, lai, user, false);
3463 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3464 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3465 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3466 }
3467
3468 // from the db
3469 if (TextUtils.isEmpty(info.title) && c != null) {
3470 info.title = c.getString(titleIndex);
3471 }
3472
3473 // fall back to the class name of the activity
3474 if (info.title == null) {
3475 info.title = componentName.getClassName();
3476 }
3477
3478 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3479 info.user = user;
3480 info.contentDescription = mUserManager.getBadgedLabelForUser(
3481 info.title.toString(), info.user);
3482 return info;
3483 }
3484
3485 static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
3486 ItemInfoFilter f) {
3487 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3488 for (ItemInfo i : infos) {
3489 if (i instanceof ShortcutInfo) {
3490 ShortcutInfo info = (ShortcutInfo) i;
3491 ComponentName cn = info.getTargetComponent();
3492 if (cn != null && f.filterItem(null, info, cn)) {
3493 filtered.add(info);
3494 }
3495 } else if (i instanceof FolderInfo) {
3496 FolderInfo info = (FolderInfo) i;
3497 for (ShortcutInfo s : info.contents) {
3498 ComponentName cn = s.getTargetComponent();
3499 if (cn != null && f.filterItem(info, s, cn)) {
3500 filtered.add(s);
3501 }
3502 }
3503 } else if (i instanceof LauncherAppWidgetInfo) {
3504 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3505 ComponentName cn = info.providerName;
3506 if (cn != null && f.filterItem(null, info, cn)) {
3507 filtered.add(info);
3508 }
3509 }
3510 }
3511 return new ArrayList<ItemInfo>(filtered);
3512 }
3513
3514 private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3515 final UserHandleCompat user) {
3516 ItemInfoFilter filter = new ItemInfoFilter() {
3517 @Override
3518 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3519 if (info.user == null) {
3520 return cn.equals(cname);
3521 } else {
3522 return cn.equals(cname) && info.user.equals(user);
3523 }
3524 }
3525 };
3526 return filterItemInfos(sBgItemsIdMap.values(), filter);
3527 }
3528
3529 /**
3530 * Make an ShortcutInfo object for a shortcut that isn't an application.
3531 */
3532 private ShortcutInfo getShortcutInfo(Cursor c, Context context,
3533 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
3534 int titleIndex) {
3535
3536 Bitmap icon = null;
3537 final ShortcutInfo info = new ShortcutInfo();
3538 // Non-app shortcuts are only supported for current user.
3539 info.user = UserHandleCompat.myUserHandle();
3540 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3541
3542 // TODO: If there's an explicit component and we can't install that, delete it.
3543
3544 info.title = c.getString(titleIndex);
3545
3546 int iconType = c.getInt(iconTypeIndex);
3547 switch (iconType) {
3548 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
3549 String packageName = c.getString(iconPackageIndex);
3550 String resourceName = c.getString(iconResourceIndex);
3551 info.customIcon = false;
3552 // the resource
3553 icon = Utilities.createIconBitmap(packageName, resourceName, mIconCache, context);
3554 // the db
3555 if (icon == null) {
3556 icon = Utilities.createIconBitmap(c, iconIndex, context);
3557 }
3558 // the fallback icon
3559 if (icon == null) {
3560 icon = mIconCache.getDefaultIcon(info.user);
3561 info.usingFallbackIcon = true;
3562 }
3563 break;
3564 case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
3565 icon = Utilities.createIconBitmap(c, iconIndex, context);
3566 if (icon == null) {
3567 icon = mIconCache.getDefaultIcon(info.user);
3568 info.customIcon = false;
3569 info.usingFallbackIcon = true;
3570 } else {
3571 info.customIcon = true;
3572 }
3573 break;
3574 default:
3575 icon = mIconCache.getDefaultIcon(info.user);
3576 info.usingFallbackIcon = true;
3577 info.customIcon = false;
3578 break;
3579 }
3580 info.setIcon(icon);
3581 return info;
3582 }
3583
3584 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3585 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3586 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3587 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3588
3589 if (intent == null) {
3590 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3591 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3592 return null;
3593 }
3594
3595 Bitmap icon = null;
3596 boolean customIcon = false;
3597 ShortcutIconResource iconResource = null;
3598
3599 if (bitmap instanceof Bitmap) {
3600 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3601 customIcon = true;
3602 } else {
3603 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3604 if (extra instanceof ShortcutIconResource) {
3605 iconResource = (ShortcutIconResource) extra;
3606 icon = Utilities.createIconBitmap(iconResource.packageName,
3607 iconResource.resourceName, mIconCache, context);
3608 }
3609 }
3610
3611 final ShortcutInfo info = new ShortcutInfo();
3612
3613 // Only support intents for current user for now. Intents sent from other
3614 // users wouldn't get here without intent forwarding anyway.
3615 info.user = UserHandleCompat.myUserHandle();
3616 if (icon == null) {
3617 icon = mIconCache.getDefaultIcon(info.user);
3618 info.usingFallbackIcon = true;
3619 }
3620 info.setIcon(icon);
3621
3622 info.title = name;
3623 info.contentDescription = mUserManager.getBadgedLabelForUser(
3624 info.title.toString(), info.user);
3625 info.intent = intent;
3626 info.customIcon = customIcon;
3627 info.iconResource = iconResource;
3628
3629 return info;
3630 }
3631
3632 /**
3633 * Return an existing FolderInfo object if we have encountered this ID previously,
3634 * or make a new one.
3635 */
3636 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
3637 // See if a placeholder was created for us already
3638 FolderInfo folderInfo = folders.get(id);
3639 if (folderInfo == null) {
3640 // No placeholder -- create a new instance
3641 folderInfo = new FolderInfo();
3642 folders.put(id, folderInfo);
3643 }
3644 return folderInfo;
3645 }
3646
3647 public static final Comparator<AppInfo> getAppNameComparator() {
3648 final Collator collator = Collator.getInstance();
3649 return new Comparator<AppInfo>() {
3650 public final int compare(AppInfo a, AppInfo b) {
3651 if (a.user.equals(b.user)) {
3652 int result = collator.compare(a.title.toString().trim(),
3653 b.title.toString().trim());
3654 if (result == 0) {
3655 result = a.componentName.compareTo(b.componentName);
3656 }
3657 return result;
3658 } else {
3659 // TODO Need to figure out rules for sorting
3660 // profiles, this puts work second.
3661 return a.user.toString().compareTo(b.user.toString());
3662 }
3663 }
3664 };
3665 }
3666 public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
3667 = new Comparator<AppInfo>() {
3668 public final int compare(AppInfo a, AppInfo b) {
3669 if (a.firstInstallTime < b.firstInstallTime) return 1;
3670 if (a.firstInstallTime > b.firstInstallTime) return -1;
3671 return 0;
3672 }
3673 };
3674 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
3675 if (info.activityInfo != null) {
3676 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
3677 } else {
3678 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
3679 }
3680 }
3681
3682 public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
3683 private final AppWidgetManagerCompat mManager;
3684 private final PackageManager mPackageManager;
3685 private final HashMap<Object, String> mLabelCache;
3686 private final Collator mCollator;
3687
3688 WidgetAndShortcutNameComparator(Context context) {
3689 mManager = AppWidgetManagerCompat.getInstance(context);
3690 mPackageManager = context.getPackageManager();
3691 mLabelCache = new HashMap<Object, String>();
3692 mCollator = Collator.getInstance();
3693 }
3694 public final int compare(Object a, Object b) {
3695 String labelA, labelB;
3696 if (mLabelCache.containsKey(a)) {
3697 labelA = mLabelCache.get(a);
3698 } else {
3699 labelA = (a instanceof LauncherAppWidgetProviderInfo)
3700 ? mManager.loadLabel((LauncherAppWidgetProviderInfo) a)
3701 : ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
3702 mLabelCache.put(a, labelA);
3703 }
3704 if (mLabelCache.containsKey(b)) {
3705 labelB = mLabelCache.get(b);
3706 } else {
3707 labelB = (b instanceof LauncherAppWidgetProviderInfo)
3708 ? mManager.loadLabel((LauncherAppWidgetProviderInfo) b)
3709 : ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
3710 mLabelCache.put(b, labelB);
3711 }
3712 return mCollator.compare(labelA, labelB);
3713 }
3714 };
3715
3716 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3717 return (provider != null) && (provider.provider != null)
3718 && (provider.provider.getPackageName() != null);
3719 }
3720
3721 public void dumpState() {
3722 Log.d(TAG, "mCallbacks=" + mCallbacks);
3723 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3724 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3725 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3726 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3727 if (mLoaderTask != null) {
3728 mLoaderTask.dumpState();
3729 } else {
3730 Log.d(TAG, "mLoaderTask=null");
3731 }
3732 }
3733
3734 public Callbacks getCallback() {
3735 return mCallbacks != null ? mCallbacks.get() : null;
3736 }
3737 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetProviderInfo;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.ContentProviderClient;
24 import android.content.ContentProviderOperation;
25 import android.content.ContentResolver;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.Intent.ShortcutIconResource;
30 import android.content.IntentFilter;
31 import android.content.SharedPreferences;
32 import android.content.pm.PackageManager;
33 import android.content.pm.ProviderInfo;
34 import android.content.pm.ResolveInfo;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.database.Cursor;
38 import android.graphics.Bitmap;
39 import android.graphics.Rect;
40 import android.net.Uri;
41 import android.os.Environment;
42 import android.os.Handler;
43 import android.os.HandlerThread;
44 import android.os.Parcelable;
45 import android.os.Process;
46 import android.os.RemoteException;
47 import android.os.SystemClock;
48 import android.provider.BaseColumns;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.util.LongSparseArray;
52 import android.util.Pair;
53
54 import com.android.launcher3.compat.AppWidgetManagerCompat;
55 import com.android.launcher3.compat.LauncherActivityInfoCompat;
56 import com.android.launcher3.compat.LauncherAppsCompat;
57 import com.android.launcher3.compat.PackageInstallerCompat;
58 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
59 import com.android.launcher3.compat.UserHandleCompat;
60 import com.android.launcher3.compat.UserManagerCompat;
61
62 import java.lang.ref.WeakReference;
63 import java.net.URISyntaxException;
64 import java.security.InvalidParameterException;
65 import java.text.Collator;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collection;
69 import java.util.Collections;
70 import java.util.Comparator;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Iterator;
74 import java.util.List;
75 import java.util.Map.Entry;
76 import java.util.Set;
77
78 /**
79 * Maintains in-memory state of the Launcher. It is expected that there should be only one
80 * LauncherModel object held in a static. Also provide APIs for updating the database state
81 * for the Launcher.
82 */
83 public class LauncherModel extends BroadcastReceiver
84 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
85 static final boolean DEBUG_LOADERS = false;
86 private static final boolean DEBUG_RECEIVER = false;
87 private static final boolean REMOVE_UNRESTORED_ICONS = true;
88 private static final boolean ADD_MANAGED_PROFILE_SHORTCUTS = false;
89
90 static final String TAG = "Launcher.Model";
91
92 public static final int LOADER_FLAG_NONE = 0;
93 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
94 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
95
96 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
97 private static final long INVALID_SCREEN_ID = -1L;
98
99 private final boolean mAppsCanBeOnRemoveableStorage;
100 private final boolean mOldContentProviderExists;
101
102 private final LauncherAppState mApp;
103 private final Object mLock = new Object();
104 private DeferredHandler mHandler = new DeferredHandler();
105 private LoaderTask mLoaderTask;
106 private boolean mIsLoaderTaskRunning;
107
108 /**
109 * Maintain a set of packages per user, for which we added a shortcut on the workspace.
110 */
111 private static final String INSTALLED_SHORTCUTS_SET_PREFIX = "installed_shortcuts_set_for_user_";
112
113 // Specific runnable types that are run on the main thread deferred handler, this allows us to
114 // clear all queued binding runnables when the Launcher activity is destroyed.
115 private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
116 private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
117
118 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
119
120 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
121 static {
122 sWorkerThread.start();
123 }
124 private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
125
126 // We start off with everything not loaded. After that, we assume that
127 // our monitoring of the package manager provides all updates and we never
128 // need to do a requery. These are only ever touched from the loader thread.
129 private boolean mWorkspaceLoaded;
130 private boolean mAllAppsLoaded;
131
132 // When we are loading pages synchronously, we can't just post the binding of items on the side
133 // pages as this delays the rotation process. Instead, we wait for a callback from the first
134 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
135 // a normal load, we also clear this set of Runnables.
136 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
137
138 private WeakReference<Callbacks> mCallbacks;
139
140 // < only access in worker thread >
141 AllAppsList mBgAllAppsList;
142
143 // The lock that must be acquired before referencing any static bg data structures. Unlike
144 // other locks, this one can generally be held long-term because we never expect any of these
145 // static data structures to be referenced outside of the worker thread except on the first
146 // load after configuration change.
147 static final Object sBgLock = new Object();
148
149 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
150 // LauncherModel to their ids
151 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
152
153 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
154 // created by LauncherModel that are directly on the home screen (however, no widgets or
155 // shortcuts within folders).
156 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
157
158 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
159 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
160 new ArrayList<LauncherAppWidgetInfo>();
161
162 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
163 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
164
165 // sBgWorkspaceScreens is the ordered set of workspace screens.
166 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
167
168 // sBgWidgetProviders is the set of widget providers including custom internal widgets
169 public static HashMap<ComponentName, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
170 public static boolean sWidgetProvidersDirty;
171
172 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
173 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
174 new HashMap<UserHandleCompat, HashSet<String>>();
175
176 // </ only access in worker thread >
177
178 private IconCache mIconCache;
179
180 protected int mPreviousConfigMcc;
181
182 private final LauncherAppsCompat mLauncherApps;
183 private final UserManagerCompat mUserManager;
184
185 public interface Callbacks {
186 public boolean setLoadOnResume();
187 public int getCurrentWorkspaceScreen();
188 public void startBinding();
189 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
190 boolean forceAnimateIcons);
191 public void bindScreens(ArrayList<Long> orderedScreenIds);
192 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
193 public void bindFolders(HashMap<Long,FolderInfo> folders);
194 public void finishBindingItems();
195 public void bindAppWidget(LauncherAppWidgetInfo info);
196 public void bindAllApplications(ArrayList<AppInfo> apps);
197 public void bindAppsAdded(ArrayList<Long> newScreens,
198 ArrayList<ItemInfo> addNotAnimated,
199 ArrayList<ItemInfo> addAnimated,
200 ArrayList<AppInfo> addedApps);
201 public void bindAppsUpdated(ArrayList<AppInfo> apps);
202 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
203 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
204 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
205 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo);
206 public void updatePackageBadge(String packageName);
207 public void bindComponentsRemoved(ArrayList<String> packageNames,
208 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
209 public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
210 public void bindSearchablesChanged();
211 public boolean isAllAppsButtonRank(int rank);
212 public void onPageBoundSynchronously(int page);
213 public void dumpLogsToLocalData();
214 public void bindAddPendingItem(PendingAddItemInfo info, long container, long screenId,
215 int[] cell, int spanX, int spanY);
216 }
217
218 public interface ItemInfoFilter {
219 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
220 }
221
222 public interface ScreenPosProvider {
223 int getScreenIndex(ArrayList<Long> screenIDs);
224 }
225
226 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
227 Context context = app.getContext();
228
229 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
230 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
231 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
232 // resource string.
233 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
234 ProviderInfo providerInfo =
235 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
236 ProviderInfo redirectProvider =
237 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
238
239 Log.d(TAG, "Old launcher provider: " + oldProvider);
240 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
241
242 if (mOldContentProviderExists) {
243 Log.d(TAG, "Old launcher provider exists.");
244 } else {
245 Log.d(TAG, "Old launcher provider does not exist.");
246 }
247
248 mApp = app;
249 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
250 mIconCache = iconCache;
251
252 final Resources res = context.getResources();
253 Configuration config = res.getConfiguration();
254 mPreviousConfigMcc = config.mcc;
255 mLauncherApps = LauncherAppsCompat.getInstance(context);
256 mUserManager = UserManagerCompat.getInstance(context);
257 }
258
259 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
260 * posted on the main thread handler. */
261 private void runOnMainThread(Runnable r) {
262 runOnMainThread(r, 0);
263 }
264 private void runOnMainThread(Runnable r, int type) {
265 if (sWorkerThread.getThreadId() == Process.myTid()) {
266 // If we are on the worker thread, post onto the main handler
267 mHandler.post(r);
268 } else {
269 r.run();
270 }
271 }
272
273 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
274 * posted on the worker thread handler. */
275 private static void runOnWorkerThread(Runnable r) {
276 if (sWorkerThread.getThreadId() == Process.myTid()) {
277 r.run();
278 } else {
279 // If we are not on the worker thread, then post to the worker handler
280 sWorker.post(r);
281 }
282 }
283
284 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
285 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
286 }
287
288 public void setPackageState(final ArrayList<PackageInstallInfo> installInfo) {
289 // Process the updated package state
290 Runnable r = new Runnable() {
291 public void run() {
292 Callbacks callbacks = getCallback();
293 if (callbacks != null) {
294 callbacks.updatePackageState(installInfo);
295 }
296 }
297 };
298 mHandler.post(r);
299 }
300
301 public void updatePackageBadge(final String packageName) {
302 // Process the updated package badge
303 Runnable r = new Runnable() {
304 public void run() {
305 Callbacks callbacks = getCallback();
306 if (callbacks != null) {
307 callbacks.updatePackageBadge(packageName);
308 }
309 }
310 };
311 mHandler.post(r);
312 }
313
314 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
315 final Callbacks callbacks = getCallback();
316
317 if (allAppsApps == null) {
318 throw new RuntimeException("allAppsApps must not be null");
319 }
320 if (allAppsApps.isEmpty()) {
321 return;
322 }
323
324 // Process the newly added applications and add them to the database first
325 Runnable r = new Runnable() {
326 public void run() {
327 runOnMainThread(new Runnable() {
328 public void run() {
329 Callbacks cb = getCallback();
330 if (callbacks == cb && cb != null) {
331 callbacks.bindAppsAdded(null, null, null, allAppsApps);
332 }
333 }
334 });
335 }
336 };
337 runOnWorkerThread(r);
338 }
339
340 public void addAndBindAddedWorkspaceApps(final Context context,
341 final ArrayList<ItemInfo> workspaceApps) {
342 addAndBindAddedWorkspaceApps(context, workspaceApps,
343 new ScreenPosProvider() {
344
345 @Override
346 public int getScreenIndex(ArrayList<Long> screenIDs) {
347 return screenIDs.isEmpty() ? 0 : 1;
348 }
349 }, 1, false);
350 }
351
352 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<Rect> occupiedPos,
353 int[] xy, int spanX, int spanY) {
354 LauncherAppState app = LauncherAppState.getInstance();
355 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
356 final int xCount = (int) grid.numColumns;
357 final int yCount = (int) grid.numRows;
358 boolean[][] occupied = new boolean[xCount][yCount];
359 if (occupiedPos != null) {
360 for (Rect r : occupiedPos) {
361 for (int x = r.left; 0 <= x && x < r.right && x < xCount; x++) {
362 for (int y = r.top; 0 <= y && y < r.bottom && y < yCount; y++) {
363 occupied[x][y] = true;
364 }
365 }
366 }
367 }
368 return CellLayout.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
369 }
370
371 /**
372 * Find a position on the screen for the given size or adds a new screen.
373 * @return screenId and the coordinates for the item.
374 */
375 private static Pair<Long, int[]> findSpaceForItem(
376 Context context,
377 ScreenPosProvider preferredScreen,
378 int fallbackStartScreen,
379 ArrayList<Long> workspaceScreens,
380 ArrayList<Long> addedWorkspaceScreensFinal,
381 int spanX, int spanY) {
382 // Load position of items which are on the desktop. We can't use sBgItemsIdMap because
383 // loadWorkspace() may not have been called.
384 final ContentResolver cr = context.getContentResolver();
385 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
386 new String[] {
387 LauncherSettings.Favorites.SCREEN,
388 LauncherSettings.Favorites.CELLX,
389 LauncherSettings.Favorites.CELLY,
390 LauncherSettings.Favorites.SPANX,
391 LauncherSettings.Favorites.SPANY,
392 LauncherSettings.Favorites.CONTAINER
393 },
394 "container=?",
395 new String[] { Integer.toString(LauncherSettings.Favorites.CONTAINER_DESKTOP) },
396 null);
397
398 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
399 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
400 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
401 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
402 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
403 LongSparseArray<ArrayList<Rect>> screenItems = new LongSparseArray<ArrayList<Rect>>();
404 try {
405 while (c.moveToNext()) {
406 Rect rect = new Rect();
407 rect.left = c.getInt(cellXIndex);
408 rect.top = c.getInt(cellYIndex);
409 rect.right = rect.left + Math.max(1, c.getInt(spanXIndex));
410 rect.bottom = rect.top + Math.max(1, c.getInt(spanYIndex));
411
412 long screenId = c.getInt(screenIndex);
413 ArrayList<Rect> items = screenItems.get(screenId);
414 if (items == null) {
415 items = new ArrayList<Rect>();
416 screenItems.put(screenId, items);
417 }
418 items.add(rect);
419 }
420 } catch (Exception e) {
421 screenItems.clear();
422 } finally {
423 c.close();
424 }
425
426 // Find appropriate space for the item.
427 long screenId = 0;
428 int[] cordinates = new int[2];
429 boolean found = false;
430
431 int screenCount = workspaceScreens.size();
432 // First check the preferred screen.
433 int preferredScreenIndex = preferredScreen.getScreenIndex(workspaceScreens);
434 if (preferredScreenIndex < screenCount) {
435 screenId = workspaceScreens.get(preferredScreenIndex);
436 found = findNextAvailableIconSpaceInScreen(
437 screenItems.get(screenId), cordinates, spanX, spanY);
438 }
439
440 if (!found) {
441 // Search on any of the screens.
442 for (int screen = fallbackStartScreen; screen < screenCount; screen++) {
443 screenId = workspaceScreens.get(screen);
444 if (findNextAvailableIconSpaceInScreen(
445 screenItems.get(screenId), cordinates, spanX, spanY)) {
446 // We found a space for it
447 found = true;
448 break;
449 }
450 }
451 }
452
453 if (!found) {
454 // Still no position found. Add a new screen to the end.
455 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
456
457 // Save the screen id for binding in the workspace
458 workspaceScreens.add(screenId);
459 addedWorkspaceScreensFinal.add(screenId);
460
461 // If we still can't find an empty space, then God help us all!!!
462 if (!findNextAvailableIconSpaceInScreen(
463 screenItems.get(screenId), cordinates, spanX, spanY)) {
464 throw new RuntimeException("Can't find space to add the item");
465 }
466 }
467 return Pair.create(screenId, cordinates);
468 }
469
470 /**
471 * Adds the provided items to the workspace.
472 * @param preferredScreen the screen where we should try to add the app first
473 * @param fallbackStartScreen the screen to start search for empty space if
474 * preferredScreen is not available.
475 */
476 public void addAndBindPendingItem(
477 final Context context,
478 final PendingAddItemInfo addInfo,
479 final ScreenPosProvider preferredScreen,
480 final int fallbackStartScreen) {
481 final Callbacks callbacks = getCallback();
482 // Process the newly added applications and add them to the database first
483 Runnable r = new Runnable() {
484 public void run() {
485 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
486 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
487
488 // Find appropriate space for the item.
489 Pair<Long, int[]> coords = findSpaceForItem(context, preferredScreen,
490 fallbackStartScreen, workspaceScreens, addedWorkspaceScreensFinal,
491 addInfo.spanX,
492 addInfo.spanY);
493 final long screenId = coords.first;
494 final int[] cordinates = coords.second;
495
496 // Update the workspace screens
497 updateWorkspaceScreenOrder(context, workspaceScreens);
498 runOnMainThread(new Runnable() {
499 public void run() {
500 Callbacks cb = getCallback();
501 if (callbacks == cb && cb != null) {
502 cb.bindAddScreens(addedWorkspaceScreensFinal);
503 cb.bindAddPendingItem(addInfo,
504 LauncherSettings.Favorites.CONTAINER_DESKTOP,
505 screenId, cordinates, addInfo.spanX, addInfo.spanY);
506 }
507 }
508 });
509 }
510 };
511 runOnWorkerThread(r);
512 }
513
514 /**
515 * Adds the provided items to the workspace.
516 * @param preferredScreen the screen where we should try to add the app first
517 * @param fallbackStartScreen the screen to start search for empty space if
518 * preferredScreen is not available.
519 */
520 public void addAndBindAddedWorkspaceApps(final Context context,
521 final ArrayList<ItemInfo> workspaceApps,
522 final ScreenPosProvider preferredScreen,
523 final int fallbackStartScreen,
524 final boolean allowDuplicate) {
525 final Callbacks callbacks = getCallback();
526 if (workspaceApps.isEmpty()) {
527 return;
528 }
529 // Process the newly added applications and add them to the database first
530 Runnable r = new Runnable() {
531 public void run() {
532 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
533 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
534
535 // Get the list of workspace screens. We need to append to this list and
536 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
537 // called.
538 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
539 synchronized(sBgLock) {
540 for (ItemInfo item : workspaceApps) {
541 if (!allowDuplicate) {
542 // Short-circuit this logic if the icon exists somewhere on the workspace
543 if (shortcutExists(context, item.title.toString(),
544 item.getIntent(), item.user)) {
545 continue;
546 }
547 }
548
549 // Find appropriate space for the item.
550 Pair<Long, int[]> coords = findSpaceForItem(context, preferredScreen,
551 fallbackStartScreen, workspaceScreens, addedWorkspaceScreensFinal,
552 1, 1);
553 long screenId = coords.first;
554 int[] cordinates = coords.second;
555
556 ShortcutInfo shortcutInfo;
557 if (item instanceof ShortcutInfo) {
558 shortcutInfo = (ShortcutInfo) item;
559 } else if (item instanceof AppInfo) {
560 shortcutInfo = ((AppInfo) item).makeShortcut();
561 } else {
562 throw new RuntimeException("Unexpected info type");
563 }
564
565 // Add the shortcut to the db
566 addItemToDatabase(context, shortcutInfo,
567 LauncherSettings.Favorites.CONTAINER_DESKTOP,
568 screenId, cordinates[0], cordinates[1], false);
569 // Save the ShortcutInfo for binding in the workspace
570 addedShortcutsFinal.add(shortcutInfo);
571 }
572 }
573
574 // Update the workspace screens
575 updateWorkspaceScreenOrder(context, workspaceScreens);
576
577 if (!addedShortcutsFinal.isEmpty()) {
578 runOnMainThread(new Runnable() {
579 public void run() {
580 Callbacks cb = getCallback();
581 if (callbacks == cb && cb != null) {
582 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
583 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
584 if (!addedShortcutsFinal.isEmpty()) {
585 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
586 long lastScreenId = info.screenId;
587 for (ItemInfo i : addedShortcutsFinal) {
588 if (i.screenId == lastScreenId) {
589 addAnimated.add(i);
590 } else {
591 addNotAnimated.add(i);
592 }
593 }
594 }
595 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
596 addNotAnimated, addAnimated, null);
597 }
598 }
599 });
600 }
601 }
602 };
603 runOnWorkerThread(r);
604 }
605
606 public void unbindItemInfosAndClearQueuedBindRunnables() {
607 if (sWorkerThread.getThreadId() == Process.myTid()) {
608 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
609 "main thread");
610 }
611
612 // Clear any deferred bind runnables
613 synchronized (mDeferredBindRunnables) {
614 mDeferredBindRunnables.clear();
615 }
616 // Remove any queued bind runnables
617 mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
618 // Unbind all the workspace items
619 unbindWorkspaceItemsOnMainThread();
620 }
621
622 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
623 void unbindWorkspaceItemsOnMainThread() {
624 // Ensure that we don't use the same workspace items data structure on the main thread
625 // by making a copy of workspace items first.
626 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
627 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
628 synchronized (sBgLock) {
629 tmpWorkspaceItems.addAll(sBgWorkspaceItems);
630 tmpAppWidgets.addAll(sBgAppWidgets);
631 }
632 Runnable r = new Runnable() {
633 @Override
634 public void run() {
635 for (ItemInfo item : tmpWorkspaceItems) {
636 item.unbind();
637 }
638 for (ItemInfo item : tmpAppWidgets) {
639 item.unbind();
640 }
641 }
642 };
643 runOnMainThread(r);
644 }
645
646 /**
647 * Adds an item to the DB if it was not created previously, or move it to a new
648 * <container, screen, cellX, cellY>
649 */
650 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
651 long screenId, int cellX, int cellY) {
652 if (item.container == ItemInfo.NO_ID) {
653 // From all apps
654 addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
655 } else {
656 // From somewhere else
657 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
658 }
659 }
660
661 static void checkItemInfoLocked(
662 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
663 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
664 if (modelItem != null && item != modelItem) {
665 // check all the data is consistent
666 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
667 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
668 ShortcutInfo shortcut = (ShortcutInfo) item;
669 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
670 modelShortcut.intent.filterEquals(shortcut.intent) &&
671 modelShortcut.id == shortcut.id &&
672 modelShortcut.itemType == shortcut.itemType &&
673 modelShortcut.container == shortcut.container &&
674 modelShortcut.screenId == shortcut.screenId &&
675 modelShortcut.cellX == shortcut.cellX &&
676 modelShortcut.cellY == shortcut.cellY &&
677 modelShortcut.spanX == shortcut.spanX &&
678 modelShortcut.spanY == shortcut.spanY &&
679 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
680 (modelShortcut.dropPos != null &&
681 shortcut.dropPos != null &&
682 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
683 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
684 // For all intents and purposes, this is the same object
685 return;
686 }
687 }
688
689 // the modelItem needs to match up perfectly with item if our model is
690 // to be consistent with the database-- for now, just require
691 // modelItem == item or the equality check above
692 String msg = "item: " + ((item != null) ? item.toString() : "null") +
693 "modelItem: " +
694 ((modelItem != null) ? modelItem.toString() : "null") +
695 "Error: ItemInfo passed to checkItemInfo doesn't match original";
696 RuntimeException e = new RuntimeException(msg);
697 if (stackTrace != null) {
698 e.setStackTrace(stackTrace);
699 }
700 throw e;
701 }
702 }
703
704 static void checkItemInfo(final ItemInfo item) {
705 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
706 final long itemId = item.id;
707 Runnable r = new Runnable() {
708 public void run() {
709 synchronized (sBgLock) {
710 checkItemInfoLocked(itemId, item, stackTrace);
711 }
712 }
713 };
714 runOnWorkerThread(r);
715 }
716
717 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
718 final ItemInfo item, final String callingFunction) {
719 final long itemId = item.id;
720 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
721 final ContentResolver cr = context.getContentResolver();
722
723 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
724 Runnable r = new Runnable() {
725 public void run() {
726 cr.update(uri, values, null, null);
727 updateItemArrays(item, itemId, stackTrace);
728 }
729 };
730 runOnWorkerThread(r);
731 }
732
733 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
734 final ArrayList<ItemInfo> items, final String callingFunction) {
735 final ContentResolver cr = context.getContentResolver();
736
737 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
738 Runnable r = new Runnable() {
739 public void run() {
740 ArrayList<ContentProviderOperation> ops =
741 new ArrayList<ContentProviderOperation>();
742 int count = items.size();
743 for (int i = 0; i < count; i++) {
744 ItemInfo item = items.get(i);
745 final long itemId = item.id;
746 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
747 ContentValues values = valuesList.get(i);
748
749 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
750 updateItemArrays(item, itemId, stackTrace);
751
752 }
753 try {
754 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
755 } catch (Exception e) {
756 e.printStackTrace();
757 }
758 }
759 };
760 runOnWorkerThread(r);
761 }
762
763 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
764 // Lock on mBgLock *after* the db operation
765 synchronized (sBgLock) {
766 checkItemInfoLocked(itemId, item, stackTrace);
767
768 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
769 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
770 // Item is in a folder, make sure this folder exists
771 if (!sBgFolders.containsKey(item.container)) {
772 // An items container is being set to a that of an item which is not in
773 // the list of Folders.
774 String msg = "item: " + item + " container being set to: " +
775 item.container + ", not in the list of folders";
776 Log.e(TAG, msg);
777 }
778 }
779
780 // Items are added/removed from the corresponding FolderInfo elsewhere, such
781 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
782 // that are on the desktop, as appropriate
783 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
784 if (modelItem != null &&
785 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
786 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
787 switch (modelItem.itemType) {
788 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
789 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
790 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
791 if (!sBgWorkspaceItems.contains(modelItem)) {
792 sBgWorkspaceItems.add(modelItem);
793 }
794 break;
795 default:
796 break;
797 }
798 } else {
799 sBgWorkspaceItems.remove(modelItem);
800 }
801 }
802 }
803
804 /**
805 * Move an item in the DB to a new <container, screen, cellX, cellY>
806 */
807 static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
808 final long screenId, final int cellX, final int cellY) {
809 item.container = container;
810 item.cellX = cellX;
811 item.cellY = cellY;
812
813 // We store hotseat items in canonical form which is this orientation invariant position
814 // in the hotseat
815 if (context instanceof Launcher && screenId < 0 &&
816 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
817 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
818 } else {
819 item.screenId = screenId;
820 }
821
822 final ContentValues values = new ContentValues();
823 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
824 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
825 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
826 values.put(LauncherSettings.Favorites.RANK, item.rank);
827 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
828
829 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
830 }
831
832 /**
833 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
834 * cellX, cellY have already been updated on the ItemInfos.
835 */
836 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
837 final long container, final int screen) {
838
839 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
840 int count = items.size();
841
842 for (int i = 0; i < count; i++) {
843 ItemInfo item = items.get(i);
844 item.container = container;
845
846 // We store hotseat items in canonical form which is this orientation invariant position
847 // in the hotseat
848 if (context instanceof Launcher && screen < 0 &&
849 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
850 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
851 item.cellY);
852 } else {
853 item.screenId = screen;
854 }
855
856 final ContentValues values = new ContentValues();
857 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
858 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
859 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
860 values.put(LauncherSettings.Favorites.RANK, item.rank);
861 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
862
863 contentValues.add(values);
864 }
865 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
866 }
867
868 /**
869 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
870 */
871 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
872 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
873 item.container = container;
874 item.cellX = cellX;
875 item.cellY = cellY;
876 item.spanX = spanX;
877 item.spanY = spanY;
878
879 // We store hotseat items in canonical form which is this orientation invariant position
880 // in the hotseat
881 if (context instanceof Launcher && screenId < 0 &&
882 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
883 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
884 } else {
885 item.screenId = screenId;
886 }
887
888 final ContentValues values = new ContentValues();
889 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
890 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
891 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
892 values.put(LauncherSettings.Favorites.RANK, item.rank);
893 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
894 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
895 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
896
897 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
898 }
899
900 /**
901 * Update an item to the database in a specified container.
902 */
903 static void updateItemInDatabase(Context context, final ItemInfo item) {
904 final ContentValues values = new ContentValues();
905 item.onAddToDatabase(context, values);
906 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
907 }
908
909 /**
910 * Returns true if the shortcuts already exists in the database.
911 * we identify a shortcut by its title and intent.
912 */
913 static boolean shortcutExists(Context context, String title, Intent intent,
914 UserHandleCompat user) {
915 final ContentResolver cr = context.getContentResolver();
916 final Intent intentWithPkg, intentWithoutPkg;
917
918 if (intent.getComponent() != null) {
919 // If component is not null, an intent with null package will produce
920 // the same result and should also be a match.
921 if (intent.getPackage() != null) {
922 intentWithPkg = intent;
923 intentWithoutPkg = new Intent(intent).setPackage(null);
924 } else {
925 intentWithPkg = new Intent(intent).setPackage(
926 intent.getComponent().getPackageName());
927 intentWithoutPkg = intent;
928 }
929 } else {
930 intentWithPkg = intent;
931 intentWithoutPkg = intent;
932 }
933 String userSerial = Long.toString(UserManagerCompat.getInstance(context)
934 .getSerialNumberForUser(user));
935 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
936 new String[] { "title", "intent", "profileId" },
937 "title=? and (intent=? or intent=?) and profileId=?",
938 new String[] { title, intentWithPkg.toUri(0), intentWithoutPkg.toUri(0), userSerial },
939 null);
940 try {
941 return c.moveToFirst();
942 } finally {
943 c.close();
944 }
945 }
946
947 /**
948 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
949 */
950 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
951 final ContentResolver cr = context.getContentResolver();
952 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
953 "_id=? and (itemType=? or itemType=?)",
954 new String[] { String.valueOf(id),
955 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
956
957 try {
958 if (c.moveToFirst()) {
959 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
960 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
961 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
962 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
963 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
964 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
965
966 FolderInfo folderInfo = null;
967 switch (c.getInt(itemTypeIndex)) {
968 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
969 folderInfo = findOrMakeFolder(folderList, id);
970 break;
971 }
972
973 folderInfo.title = c.getString(titleIndex);
974 folderInfo.id = id;
975 folderInfo.container = c.getInt(containerIndex);
976 folderInfo.screenId = c.getInt(screenIndex);
977 folderInfo.cellX = c.getInt(cellXIndex);
978 folderInfo.cellY = c.getInt(cellYIndex);
979
980 return folderInfo;
981 }
982 } finally {
983 c.close();
984 }
985
986 return null;
987 }
988
989 /**
990 * Add an item to the database in a specified container. Sets the container, screen, cellX and
991 * cellY fields of the item. Also assigns an ID to the item.
992 */
993 static void addItemToDatabase(Context context, final ItemInfo item, final long container,
994 final long screenId, final int cellX, final int cellY, final boolean notify) {
995 item.container = container;
996 item.cellX = cellX;
997 item.cellY = cellY;
998 // We store hotseat items in canonical form which is this orientation invariant position
999 // in the hotseat
1000 if (context instanceof Launcher && screenId < 0 &&
1001 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1002 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1003 } else {
1004 item.screenId = screenId;
1005 }
1006
1007 final ContentValues values = new ContentValues();
1008 final ContentResolver cr = context.getContentResolver();
1009 item.onAddToDatabase(context, values);
1010
1011 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1012 values.put(LauncherSettings.Favorites._ID, item.id);
1013
1014 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1015 Runnable r = new Runnable() {
1016 public void run() {
1017 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
1018 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
1019
1020 // Lock on mBgLock *after* the db operation
1021 synchronized (sBgLock) {
1022 checkItemInfoLocked(item.id, item, stackTrace);
1023 sBgItemsIdMap.put(item.id, item);
1024 switch (item.itemType) {
1025 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1026 sBgFolders.put(item.id, (FolderInfo) item);
1027 // Fall through
1028 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1029 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1030 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1031 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1032 sBgWorkspaceItems.add(item);
1033 } else {
1034 if (!sBgFolders.containsKey(item.container)) {
1035 // Adding an item to a folder that doesn't exist.
1036 String msg = "adding item: " + item + " to a folder that " +
1037 " doesn't exist";
1038 Log.e(TAG, msg);
1039 }
1040 }
1041 break;
1042 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1043 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1044 break;
1045 }
1046 }
1047 }
1048 };
1049 runOnWorkerThread(r);
1050 }
1051
1052 /**
1053 * Creates a new unique child id, for a given cell span across all layouts.
1054 */
1055 static int getCellLayoutChildId(
1056 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1057 return (((int) container & 0xFF) << 24)
1058 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1059 }
1060
1061 private static ArrayList<ItemInfo> getItemsByPackageName(
1062 final String pn, final UserHandleCompat user) {
1063 ItemInfoFilter filter = new ItemInfoFilter() {
1064 @Override
1065 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1066 return cn.getPackageName().equals(pn) && info.user.equals(user);
1067 }
1068 };
1069 return filterItemInfos(sBgItemsIdMap.values(), filter);
1070 }
1071
1072 /**
1073 * Removes all the items from the database corresponding to the specified package.
1074 */
1075 static void deletePackageFromDatabase(Context context, final String pn,
1076 final UserHandleCompat user) {
1077 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1078 }
1079
1080 /**
1081 * Removes the specified item from the database
1082 * @param context
1083 * @param item
1084 */
1085 static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1086 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1087 items.add(item);
1088 deleteItemsFromDatabase(context, items);
1089 }
1090
1091 /**
1092 * Removes the specified items from the database
1093 * @param context
1094 * @param item
1095 */
1096 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1097 final ContentResolver cr = context.getContentResolver();
1098
1099 Runnable r = new Runnable() {
1100 public void run() {
1101 for (ItemInfo item : items) {
1102 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id, false);
1103 cr.delete(uri, null, null);
1104
1105 // Lock on mBgLock *after* the db operation
1106 synchronized (sBgLock) {
1107 switch (item.itemType) {
1108 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1109 sBgFolders.remove(item.id);
1110 for (ItemInfo info: sBgItemsIdMap.values()) {
1111 if (info.container == item.id) {
1112 // We are deleting a folder which still contains items that
1113 // think they are contained by that folder.
1114 String msg = "deleting a folder (" + item + ") which still " +
1115 "contains items (" + info + ")";
1116 Log.e(TAG, msg);
1117 }
1118 }
1119 sBgWorkspaceItems.remove(item);
1120 break;
1121 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1122 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1123 sBgWorkspaceItems.remove(item);
1124 break;
1125 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1126 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1127 break;
1128 }
1129 sBgItemsIdMap.remove(item.id);
1130 }
1131 }
1132 }
1133 };
1134 runOnWorkerThread(r);
1135 }
1136
1137 /**
1138 * Update the order of the workspace screens in the database. The array list contains
1139 * a list of screen ids in the order that they should appear.
1140 */
1141 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1142 // Log to disk
1143 Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
1144 Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
1145
1146 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1147 final ContentResolver cr = context.getContentResolver();
1148 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1149
1150 // Remove any negative screen ids -- these aren't persisted
1151 Iterator<Long> iter = screensCopy.iterator();
1152 while (iter.hasNext()) {
1153 long id = iter.next();
1154 if (id < 0) {
1155 iter.remove();
1156 }
1157 }
1158
1159 Runnable r = new Runnable() {
1160 @Override
1161 public void run() {
1162 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1163 // Clear the table
1164 ops.add(ContentProviderOperation.newDelete(uri).build());
1165 int count = screensCopy.size();
1166 for (int i = 0; i < count; i++) {
1167 ContentValues v = new ContentValues();
1168 long screenId = screensCopy.get(i);
1169 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1170 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1171 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1172 }
1173
1174 try {
1175 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1176 } catch (Exception ex) {
1177 throw new RuntimeException(ex);
1178 }
1179
1180 synchronized (sBgLock) {
1181 sBgWorkspaceScreens.clear();
1182 sBgWorkspaceScreens.addAll(screensCopy);
1183 }
1184 }
1185 };
1186 runOnWorkerThread(r);
1187 }
1188
1189 /**
1190 * Remove the contents of the specified folder from the database
1191 */
1192 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1193 final ContentResolver cr = context.getContentResolver();
1194
1195 Runnable r = new Runnable() {
1196 public void run() {
1197 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1198 // Lock on mBgLock *after* the db operation
1199 synchronized (sBgLock) {
1200 sBgItemsIdMap.remove(info.id);
1201 sBgFolders.remove(info.id);
1202 sBgWorkspaceItems.remove(info);
1203 }
1204
1205 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
1206 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1207 // Lock on mBgLock *after* the db operation
1208 synchronized (sBgLock) {
1209 for (ItemInfo childInfo : info.contents) {
1210 sBgItemsIdMap.remove(childInfo.id);
1211 }
1212 }
1213 }
1214 };
1215 runOnWorkerThread(r);
1216 }
1217
1218 /**
1219 * Set this as the current Launcher activity object for the loader.
1220 */
1221 public void initialize(Callbacks callbacks) {
1222 synchronized (mLock) {
1223 mCallbacks = new WeakReference<Callbacks>(callbacks);
1224 }
1225 }
1226
1227 @Override
1228 public void onPackageChanged(String packageName, UserHandleCompat user) {
1229 int op = PackageUpdatedTask.OP_UPDATE;
1230 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1231 user));
1232 }
1233
1234 @Override
1235 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1236 int op = PackageUpdatedTask.OP_REMOVE;
1237 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1238 user));
1239 }
1240
1241 @Override
1242 public void onPackageAdded(String packageName, UserHandleCompat user) {
1243 int op = PackageUpdatedTask.OP_ADD;
1244 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1245 user));
1246 }
1247
1248 @Override
1249 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1250 boolean replacing) {
1251 if (!replacing) {
1252 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1253 user));
1254 if (mAppsCanBeOnRemoveableStorage) {
1255 // Only rebind if we support removable storage. It catches the
1256 // case where
1257 // apps on the external sd card need to be reloaded
1258 startLoaderFromBackground();
1259 }
1260 } else {
1261 // If we are replacing then just update the packages in the list
1262 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1263 packageNames, user));
1264 }
1265 }
1266
1267 @Override
1268 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1269 boolean replacing) {
1270 if (!replacing) {
1271 enqueuePackageUpdated(new PackageUpdatedTask(
1272 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1273 user));
1274 }
1275 }
1276
1277 /**
1278 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1279 * ACTION_PACKAGE_CHANGED.
1280 */
1281 @Override
1282 public void onReceive(Context context, Intent intent) {
1283 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1284
1285 final String action = intent.getAction();
1286 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1287 // If we have changed locale we need to clear out the labels in all apps/workspace.
1288 forceReload();
1289 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1290 // Check if configuration change was an mcc/mnc change which would affect app resources
1291 // and we would need to clear out the labels in all apps/workspace. Same handling as
1292 // above for ACTION_LOCALE_CHANGED
1293 Configuration currentConfig = context.getResources().getConfiguration();
1294 if (mPreviousConfigMcc != currentConfig.mcc) {
1295 Log.d(TAG, "Reload apps on config change. curr_mcc:"
1296 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
1297 forceReload();
1298 }
1299 // Update previousConfig
1300 mPreviousConfigMcc = currentConfig.mcc;
1301 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1302 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1303 Callbacks callbacks = getCallback();
1304 if (callbacks != null) {
1305 callbacks.bindSearchablesChanged();
1306 }
1307 }
1308 }
1309
1310 void forceReload() {
1311 resetLoadedState(true, true);
1312
1313 // Do this here because if the launcher activity is running it will be restarted.
1314 // If it's not running startLoaderFromBackground will merely tell it that it needs
1315 // to reload.
1316 startLoaderFromBackground();
1317 }
1318
1319 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1320 synchronized (mLock) {
1321 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1322 // mWorkspaceLoaded to true later
1323 stopLoaderLocked();
1324 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1325 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1326 }
1327 }
1328
1329 /**
1330 * When the launcher is in the background, it's possible for it to miss paired
1331 * configuration changes. So whenever we trigger the loader from the background
1332 * tell the launcher that it needs to re-run the loader when it comes back instead
1333 * of doing it now.
1334 */
1335 public void startLoaderFromBackground() {
1336 boolean runLoader = false;
1337 Callbacks callbacks = getCallback();
1338 if (callbacks != null) {
1339 // Only actually run the loader if they're not paused.
1340 if (!callbacks.setLoadOnResume()) {
1341 runLoader = true;
1342 }
1343 }
1344 if (runLoader) {
1345 startLoader(false, PagedView.INVALID_RESTORE_PAGE);
1346 }
1347 }
1348
1349 // If there is already a loader task running, tell it to stop.
1350 // returns true if isLaunching() was true on the old task
1351 private boolean stopLoaderLocked() {
1352 boolean isLaunching = false;
1353 LoaderTask oldTask = mLoaderTask;
1354 if (oldTask != null) {
1355 if (oldTask.isLaunching()) {
1356 isLaunching = true;
1357 }
1358 oldTask.stopLocked();
1359 }
1360 return isLaunching;
1361 }
1362
1363 public boolean isCurrentCallbacks(Callbacks callbacks) {
1364 return (mCallbacks != null && mCallbacks.get() == callbacks);
1365 }
1366
1367 public void startLoader(boolean isLaunching, int synchronousBindPage) {
1368 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
1369 }
1370
1371 public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
1372 synchronized (mLock) {
1373 if (DEBUG_LOADERS) {
1374 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
1375 }
1376
1377 // Clear any deferred bind-runnables from the synchronized load process
1378 // We must do this before any loading/binding is scheduled below.
1379 synchronized (mDeferredBindRunnables) {
1380 mDeferredBindRunnables.clear();
1381 }
1382
1383 // Don't bother to start the thread if we know it's not going to do anything
1384 if (mCallbacks != null && mCallbacks.get() != null) {
1385 // If there is already one running, tell it to stop.
1386 // also, don't downgrade isLaunching if we're already running
1387 isLaunching = isLaunching || stopLoaderLocked();
1388 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
1389 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1390 && mAllAppsLoaded && mWorkspaceLoaded) {
1391 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1392 } else {
1393 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1394 sWorker.post(mLoaderTask);
1395 }
1396 }
1397 }
1398 }
1399
1400 void bindRemainingSynchronousPages() {
1401 // Post the remaining side pages to be loaded
1402 if (!mDeferredBindRunnables.isEmpty()) {
1403 Runnable[] deferredBindRunnables = null;
1404 synchronized (mDeferredBindRunnables) {
1405 deferredBindRunnables = mDeferredBindRunnables.toArray(
1406 new Runnable[mDeferredBindRunnables.size()]);
1407 mDeferredBindRunnables.clear();
1408 }
1409 for (final Runnable r : deferredBindRunnables) {
1410 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
1411 }
1412 }
1413 }
1414
1415 public void stopLoader() {
1416 synchronized (mLock) {
1417 if (mLoaderTask != null) {
1418 mLoaderTask.stopLocked();
1419 }
1420 }
1421 }
1422
1423 /**
1424 * Loads the workspace screen ids in an ordered list.
1425 */
1426 private static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1427 final ContentResolver contentResolver = context.getContentResolver();
1428 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1429
1430 // Get screens ordered by rank.
1431 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1432 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1433 ArrayList<Long> screenIds = new ArrayList<Long>();
1434 try {
1435 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1436 while (sc.moveToNext()) {
1437 try {
1438 screenIds.add(sc.getLong(idIndex));
1439 } catch (Exception e) {
1440 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1441 + " - invalid screens: " + e, true);
1442 }
1443 }
1444 } finally {
1445 sc.close();
1446 }
1447 return screenIds;
1448 }
1449
1450 public boolean isAllAppsLoaded() {
1451 return mAllAppsLoaded;
1452 }
1453
1454 boolean isLoadingWorkspace() {
1455 synchronized (mLock) {
1456 if (mLoaderTask != null) {
1457 return mLoaderTask.isLoadingWorkspace();
1458 }
1459 }
1460 return false;
1461 }
1462
1463 /**
1464 * Runnable for the thread that loads the contents of the launcher:
1465 * - workspace icons
1466 * - widgets
1467 * - all apps icons
1468 */
1469 private class LoaderTask implements Runnable {
1470 private Context mContext;
1471 private boolean mIsLaunching;
1472 private boolean mIsLoadingAndBindingWorkspace;
1473 private boolean mStopped;
1474 private boolean mLoadAndBindStepFinished;
1475 private int mFlags;
1476
1477 LoaderTask(Context context, boolean isLaunching, int flags) {
1478 mContext = context;
1479 mIsLaunching = isLaunching;
1480 mFlags = flags;
1481 }
1482
1483 boolean isLaunching() {
1484 return mIsLaunching;
1485 }
1486
1487 boolean isLoadingWorkspace() {
1488 return mIsLoadingAndBindingWorkspace;
1489 }
1490
1491 private void loadAndBindWorkspace() {
1492 mIsLoadingAndBindingWorkspace = true;
1493
1494 // Load the workspace
1495 if (DEBUG_LOADERS) {
1496 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1497 }
1498
1499 if (!mWorkspaceLoaded) {
1500 loadWorkspace();
1501 synchronized (LoaderTask.this) {
1502 if (mStopped) {
1503 return;
1504 }
1505 mWorkspaceLoaded = true;
1506 }
1507 }
1508
1509 // Bind the workspace
1510 bindWorkspace(-1);
1511 }
1512
1513 private void waitForIdle() {
1514 // Wait until the either we're stopped or the other threads are done.
1515 // This way we don't start loading all apps until the workspace has settled
1516 // down.
1517 synchronized (LoaderTask.this) {
1518 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1519
1520 mHandler.postIdle(new Runnable() {
1521 public void run() {
1522 synchronized (LoaderTask.this) {
1523 mLoadAndBindStepFinished = true;
1524 if (DEBUG_LOADERS) {
1525 Log.d(TAG, "done with previous binding step");
1526 }
1527 LoaderTask.this.notify();
1528 }
1529 }
1530 });
1531
1532 while (!mStopped && !mLoadAndBindStepFinished) {
1533 try {
1534 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1535 // wait no longer than 1sec at a time
1536 this.wait(1000);
1537 } catch (InterruptedException ex) {
1538 // Ignore
1539 }
1540 }
1541 if (DEBUG_LOADERS) {
1542 Log.d(TAG, "waited "
1543 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1544 + "ms for previous step to finish binding");
1545 }
1546 }
1547 }
1548
1549 void runBindSynchronousPage(int synchronousBindPage) {
1550 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1551 // Ensure that we have a valid page index to load synchronously
1552 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1553 "valid page index");
1554 }
1555 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1556 // Ensure that we don't try and bind a specified page when the pages have not been
1557 // loaded already (we should load everything asynchronously in that case)
1558 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1559 }
1560 synchronized (mLock) {
1561 if (mIsLoaderTaskRunning) {
1562 // Ensure that we are never running the background loading at this point since
1563 // we also touch the background collections
1564 throw new RuntimeException("Error! Background loading is already running");
1565 }
1566 }
1567
1568 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1569 // data structures, we can't allow any other thread to touch that data, but because
1570 // this call is synchronous, we can get away with not locking).
1571
1572 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1573 // operations from the previous activity. We need to ensure that all queued operations
1574 // are executed before any synchronous binding work is done.
1575 mHandler.flush();
1576
1577 // Divide the set of loaded items into those that we are binding synchronously, and
1578 // everything else that is to be bound normally (asynchronously).
1579 bindWorkspace(synchronousBindPage);
1580 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1581 // arise from that.
1582 onlyBindAllApps();
1583 }
1584
1585 public void run() {
1586 synchronized (mLock) {
1587 mIsLoaderTaskRunning = true;
1588 }
1589 // Optimize for end-user experience: if the Launcher is up and // running with the
1590 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1591 // workspace first (default).
1592 keep_running: {
1593 // Elevate priority when Home launches for the first time to avoid
1594 // starving at boot time. Staring at a blank home is not cool.
1595 synchronized (mLock) {
1596 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
1597 (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
1598 android.os.Process.setThreadPriority(mIsLaunching
1599 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
1600 }
1601 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1602 loadAndBindWorkspace();
1603
1604 if (mStopped) {
1605 break keep_running;
1606 }
1607
1608 // Whew! Hard work done. Slow us down, and wait until the UI thread has
1609 // settled down.
1610 synchronized (mLock) {
1611 if (mIsLaunching) {
1612 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
1613 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1614 }
1615 }
1616 waitForIdle();
1617
1618 // second step
1619 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1620 loadAndBindAllApps();
1621
1622 // Restore the default thread priority after we are done loading items
1623 synchronized (mLock) {
1624 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1625 }
1626 }
1627
1628 <<<<<<< MINE
1629 if (LauncherAppState.isDisableAllApps()) {
1630 // Ensure that all the applications that are in the system are
1631 // represented on the home screen.
1632 verifyApplications();
1633 ||||||| BASE
1634 // Update the saved icons if necessary
1635 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1636 synchronized (sBgLock) {
1637 for (Object key : sBgDbIconCache.keySet()) {
1638 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1639 }
1640 sBgDbIconCache.clear();
1641 =======
1642 // Update the saved icons if necessary
1643 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1644 synchronized (sBgLock) {
1645 for (Object key : sBgDbIconCache.keySet()) {
1646 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1647 }
1648 sBgDbIconCache.clear();
1649 >>>>>>> YOURS
1650 }
1651
1652 // Clear out this reference, otherwise we end up holding it until all of the
1653 // callback runnables are done.
1654 mContext = null;
1655
1656 synchronized (mLock) {
1657 // If we are still the last one to be scheduled, remove ourselves.
1658 if (mLoaderTask == this) {
1659 mLoaderTask = null;
1660 }
1661 mIsLoaderTaskRunning = false;
1662 }
1663 }
1664
1665 public void stopLocked() {
1666 synchronized (LoaderTask.this) {
1667 mStopped = true;
1668 this.notify();
1669 }
1670 }
1671
1672 /**
1673 * Gets the callbacks object. If we've been stopped, or if the launcher object
1674 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1675 * object that was around when the deferred message was scheduled, and if there's
1676 * a new Callbacks object around then also return null. This will save us from
1677 * calling onto it with data that will be ignored.
1678 */
1679 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1680 synchronized (mLock) {
1681 if (mStopped) {
1682 return null;
1683 }
1684
1685 if (mCallbacks == null) {
1686 return null;
1687 }
1688
1689 final Callbacks callbacks = mCallbacks.get();
1690 if (callbacks != oldCallbacks) {
1691 return null;
1692 }
1693 if (callbacks == null) {
1694 Log.w(TAG, "no mCallbacks");
1695 return null;
1696 }
1697
1698 return callbacks;
1699 }
1700 }
1701
1702 // check & update map of what's occupied; used to discard overlapping/invalid items
1703 private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) {
1704 LauncherAppState app = LauncherAppState.getInstance();
1705 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1706 final int countX = (int) grid.numColumns;
1707 final int countY = (int) grid.numRows;
1708
1709 long containerIndex = item.screenId;
1710 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1711 // Return early if we detect that an item is under the hotseat button
1712 if (mCallbacks == null ||
1713 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1714 Log.e(TAG, "Error loading shortcut into hotseat " + item
1715 + " into position (" + item.screenId + ":" + item.cellX + ","
1716 + item.cellY + ") occupied by all apps");
1717 return false;
1718 }
1719
1720 final ItemInfo[][] hotseatItems =
1721 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1722
1723 if (item.screenId >= grid.numHotseatIcons) {
1724 Log.e(TAG, "Error loading shortcut " + item
1725 + " into hotseat position " + item.screenId
1726 + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1)
1727 + ")");
1728 return false;
1729 }
1730
1731 if (hotseatItems != null) {
1732 if (hotseatItems[(int) item.screenId][0] != null) {
1733 Log.e(TAG, "Error loading shortcut into hotseat " + item
1734 + " into position (" + item.screenId + ":" + item.cellX + ","
1735 + item.cellY + ") occupied by "
1736 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1737 [(int) item.screenId][0]);
1738 return false;
1739 } else {
1740 hotseatItems[(int) item.screenId][0] = item;
1741 return true;
1742 }
1743 } else {
1744 final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
1745 items[(int) item.screenId][0] = item;
1746 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1747 return true;
1748 }
1749 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1750 // Skip further checking if it is not the hotseat or workspace container
1751 return true;
1752 }
1753
1754 if (!occupied.containsKey(item.screenId)) {
1755 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1756 occupied.put(item.screenId, items);
1757 }
1758
1759 final ItemInfo[][] screens = occupied.get(item.screenId);
1760 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1761 item.cellX < 0 || item.cellY < 0 ||
1762 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1763 Log.e(TAG, "Error loading shortcut " + item
1764 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1765 + item.cellX + "," + item.cellY
1766 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1767 return false;
1768 }
1769
1770 // Check if any workspace icons overlap with each other
1771 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1772 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1773 if (screens[x][y] != null) {
1774 Log.e(TAG, "Error loading shortcut " + item
1775 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1776 + x + "," + y
1777 + ") occupied by "
1778 + screens[x][y]);
1779 return false;
1780 }
1781 }
1782 }
1783 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1784 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1785 screens[x][y] = item;
1786 }
1787 }
1788
1789 return true;
1790 }
1791
1792 /** Clears all the sBg data structures */
1793 private void clearSBgDataStructures() {
1794 synchronized (sBgLock) {
1795 sBgWorkspaceItems.clear();
1796 sBgAppWidgets.clear();
1797 sBgFolders.clear();
1798 sBgItemsIdMap.clear();
1799 sBgWorkspaceScreens.clear();
1800 }
1801 }
1802
1803 private void loadWorkspace() {
1804 // Log to disk
1805 Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
1806
1807 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1808
1809 final Context context = mContext;
1810 final ContentResolver contentResolver = context.getContentResolver();
1811 final PackageManager manager = context.getPackageManager();
1812 final boolean isSafeMode = manager.isSafeMode();
1813 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1814 final boolean isSdCardReady = context.registerReceiver(null,
1815 new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
1816
1817 LauncherAppState app = LauncherAppState.getInstance();
1818 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1819 int countX = (int) grid.numColumns;
1820 int countY = (int) grid.numRows;
1821
1822 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1823 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1824 LauncherAppState.getLauncherProvider().deleteDatabase();
1825 }
1826
1827 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1828 // append the user's Launcher2 shortcuts
1829 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1830 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1831 } else {
1832 // Make sure the default workspace is loaded
1833 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1834 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1835 }
1836
1837 synchronized (sBgLock) {
1838 clearSBgDataStructures();
1839 final HashSet<String> installingPkgs = PackageInstallerCompat
1840 .getInstance(mContext).updateAndGetActiveSessionCache();
1841
1842 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1843 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1844 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION;
1845 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1846 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1847
1848 // +1 for the hotseat (it can be larger than the workspace)
1849 // Load workspace in reverse order to ensure that latest items are loaded first (and
1850 // before any earlier duplicates)
1851 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
1852
1853 try {
1854 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1855 final int intentIndex = c.getColumnIndexOrThrow
1856 (LauncherSettings.Favorites.INTENT);
1857 final int titleIndex = c.getColumnIndexOrThrow
1858 (LauncherSettings.Favorites.TITLE);
1859 final int iconTypeIndex = c.getColumnIndexOrThrow(
1860 LauncherSettings.Favorites.ICON_TYPE);
1861 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1862 final int iconPackageIndex = c.getColumnIndexOrThrow(
1863 LauncherSettings.Favorites.ICON_PACKAGE);
1864 final int iconResourceIndex = c.getColumnIndexOrThrow(
1865 LauncherSettings.Favorites.ICON_RESOURCE);
1866 final int containerIndex = c.getColumnIndexOrThrow(
1867 LauncherSettings.Favorites.CONTAINER);
1868 final int itemTypeIndex = c.getColumnIndexOrThrow(
1869 LauncherSettings.Favorites.ITEM_TYPE);
1870 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1871 LauncherSettings.Favorites.APPWIDGET_ID);
1872 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1873 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1874 final int screenIndex = c.getColumnIndexOrThrow(
1875 LauncherSettings.Favorites.SCREEN);
1876 final int cellXIndex = c.getColumnIndexOrThrow
1877 (LauncherSettings.Favorites.CELLX);
1878 final int cellYIndex = c.getColumnIndexOrThrow
1879 (LauncherSettings.Favorites.CELLY);
1880 final int spanXIndex = c.getColumnIndexOrThrow
1881 (LauncherSettings.Favorites.SPANX);
1882 final int spanYIndex = c.getColumnIndexOrThrow(
1883 LauncherSettings.Favorites.SPANY);
1884 final int rankIndex = c.getColumnIndexOrThrow(
1885 LauncherSettings.Favorites.RANK);
1886 final int restoredIndex = c.getColumnIndexOrThrow(
1887 LauncherSettings.Favorites.RESTORED);
1888 final int profileIdIndex = c.getColumnIndexOrThrow(
1889 LauncherSettings.Favorites.PROFILE_ID);
1890 //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1891 //final int displayModeIndex = c.getColumnIndexOrThrow(
1892 // LauncherSettings.Favorites.DISPLAY_MODE);
1893
1894 ShortcutInfo info;
1895 String intentDescription;
1896 LauncherAppWidgetInfo appWidgetInfo;
1897 int container;
1898 long id;
1899 Intent intent;
1900 UserHandleCompat user;
1901
1902 while (!mStopped && c.moveToNext()) {
1903 try {
1904 int itemType = c.getInt(itemTypeIndex);
1905 boolean restored = 0 != c.getInt(restoredIndex);
1906 boolean allowMissingTarget = false;
1907
1908 switch (itemType) {
1909 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1910 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1911 id = c.getLong(idIndex);
1912 intentDescription = c.getString(intentIndex);
1913 long serialNumber = c.getInt(profileIdIndex);
1914 user = mUserManager.getUserForSerialNumber(serialNumber);
1915 int promiseType = c.getInt(restoredIndex);
1916 int disabledState = 0;
1917 boolean itemReplaced = false;
1918 if (user == null) {
1919 // User has been deleted remove the item.
1920 itemsToRemove.add(id);
1921 continue;
1922 }
1923 try {
1924 intent = Intent.parseUri(intentDescription, 0);
1925 ComponentName cn = intent.getComponent();
1926 if (cn != null && cn.getPackageName() != null) {
1927 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1928 cn.getPackageName(), user);
1929 boolean validComponent = validPkg &&
1930 launcherApps.isActivityEnabledForProfile(cn, user);
1931
1932 if (validComponent) {
1933 if (restored) {
1934 // no special handling necessary for this item
1935 restoredRows.add(id);
1936 restored = false;
1937 }
1938 } else if (validPkg) {
1939 intent = null;
1940 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1941 // We allow auto install apps to have their intent
1942 // updated after an install.
1943 intent = manager.getLaunchIntentForPackage(
1944 cn.getPackageName());
1945 if (intent != null) {
1946 ContentValues values = new ContentValues();
1947 values.put(LauncherSettings.Favorites.INTENT,
1948 intent.toUri(0));
1949 updateItem(id, values);
1950 }
1951 }
1952
1953 if (intent == null) {
1954 // The app is installed but the component is no
1955 // longer available.
1956 Launcher.addDumpLog(TAG,
1957 "Invalid component removed: " + cn, true);
1958 itemsToRemove.add(id);
1959 continue;
1960 } else {
1961 // no special handling necessary for this item
1962 restoredRows.add(id);
1963 restored = false;
1964 }
1965 } else if (restored) {
1966 // Package is not yet available but might be
1967 // installed later.
1968 Launcher.addDumpLog(TAG,
1969 "package not yet restored: " + cn, true);
1970
1971 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1972 // Restore has started once.
1973 } else if (installingPkgs.contains(cn.getPackageName())) {
1974 // App restore has started. Update the flag
1975 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1976 ContentValues values = new ContentValues();
1977 values.put(LauncherSettings.Favorites.RESTORED,
1978 promiseType);
1979 updateItem(id, values);
1980 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE🔵
1981 // This is a common app. Try to replace this.
1982 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(🔵
1983 CommonAppTypeParser parser = new CommonAppTypeParser(id, 🔵
1984 if (parser.findDefaultApp()) {
1985 // Default app found. Replace it.
1986 intent = parser.parsedIntent;
1987 cn = intent.getComponent();
1988 ContentValues values = parser.parsedValues;
1989 values.put(LauncherSettings.Favorites.RESTORED, 0);
1990 updateItem(id, values);
1991 restored = false;
1992 itemReplaced = true;
1993
1994 } else if (REMOVE_UNRESTORED_ICONS) {
1995 Launcher.addDumpLog(TAG,
1996 "Unrestored package removed: " + cn, true);
1997 itemsToRemove.add(id);
1998 continue;
1999 }
2000 } else if (REMOVE_UNRESTORED_ICONS) {
2001 Launcher.addDumpLog(TAG,
2002 "Unrestored package removed: " + cn, true);
2003 itemsToRemove.add(id);
2004 continue;
2005 }
2006 } else if (launcherApps.isAppEnabled(
2007 manager, cn.getPackageName(),
2008 PackageManager.GET_UNINSTALLED_PACKAGES)) {
2009 // Package is present but not available.
2010 allowMissingTarget = true;
2011 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
2012 } else if (!isSdCardReady) {
2013 // SdCard is not ready yet. Package might get available,
2014 // once it is ready.
2015 Launcher.addDumpLog(TAG, "Invalid package: " + cn
2016 + " (check again later)", true);
2017 HashSet<String> pkgs = sPendingPackages.get(user);
2018 if (pkgs == null) {
2019 pkgs = new HashSet<String>();
2020 sPendingPackages.put(user, pkgs);
2021 }
2022 pkgs.add(cn.getPackageName());
2023 allowMissingTarget = true;
2024 // Add the icon on the workspace anyway.
2025
2026 } else {
2027 // Do not wait for external media load anymore.
2028 // Log the invalid package, and remove it
2029 Launcher.addDumpLog(TAG,
2030 "Invalid package removed: " + cn, true);
2031 itemsToRemove.add(id);
2032 continue;
2033 }
2034 } else if (cn == null) {
2035 // For shortcuts with no component, keep them as they are
2036 restoredRows.add(id);
2037 restored = false;
2038 }
2039 } catch (URISyntaxException e) {
2040 Launcher.addDumpLog(TAG,
2041 "Invalid uri: " + intentDescription, true);
2042 continue;
2043 }
2044
2045 if (itemReplaced) {
2046 if (user.equals(UserHandleCompat.myUserHandle())) {
2047 info = getAppShortcutInfo(manager, intent, user, context, null,
2048 iconIndex, titleIndex, false);
2049 } else {
2050 // Don't replace items for other profiles.
2051 itemsToRemove.add(id);
2052 continue;
2053 }
2054 } else if (restored) {
2055 if (user.equals(UserHandleCompat.myUserHandle())) {
2056 Launcher.addDumpLog(TAG,
2057 "constructing info for partially restored package",
2058 true);
2059 info = getRestoredItemInfo(c, titleIndex, intent, promiseType);
2060 intent = getRestoredItemIntent(c, context, intent);
2061 } else {
2062 // Don't restore items for other profiles.
2063 itemsToRemove.add(id);
2064 continue;
2065 }
2066 } else if (itemType ==
2067 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2068 info = getAppShortcutInfo(manager, intent, user, context, c,
2069 iconIndex, titleIndex, allowMissingTarget);
2070 } else {
2071 info = getShortcutInfo(c, context, iconTypeIndex,
2072 iconPackageIndex, iconResourceIndex, iconIndex,
2073 titleIndex);
2074
2075 // App shortcuts that used to be automatically added to Launcher
2076 // didn't always have the correct intent flags set, so do that
2077 // here
2078 if (intent.getAction() != null &&
2079 intent.getCategories() != null &&
2080 intent.getAction().equals(Intent.ACTION_MAIN) &&
2081 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2082 intent.addFlags(
2083 Intent.FLAG_ACTIVITY_NEW_TASK |
2084 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2085 }
2086 }
2087
2088 if (info != null) {
2089 info.id = id;
2090 info.intent = intent;
2091 container = c.getInt(containerIndex);
2092 info.container = container;
2093 info.screenId = c.getInt(screenIndex);
2094 info.cellX = c.getInt(cellXIndex);
2095 info.cellY = c.getInt(cellYIndex);
2096 info.rank = c.getInt(rankIndex);
2097 info.spanX = 1;
2098 info.spanY = 1;
2099 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2100 info.isDisabled = disabledState;
2101 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2102 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2103 }
2104
2105 // check & update map of what's occupied
2106 if (!checkItemPlacement(occupied, info)) {
2107 itemsToRemove.add(id);
2108 break;
2109 }
2110
2111 switch (container) {
2112 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2113 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2114 sBgWorkspaceItems.add(info);
2115 break;
2116 default:
2117 // Item is in a user folder
2118 FolderInfo folderInfo =
2119 findOrMakeFolder(sBgFolders, container);
2120 folderInfo.add(info);
2121 break;
2122 }
2123 sBgItemsIdMap.put(info.id, info);
2124 } else {
2125 throw new RuntimeException("Unexpected null ShortcutInfo");
2126 }
2127 break;
2128
2129 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2130 id = c.getLong(idIndex);
2131 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2132
2133 folderInfo.title = c.getString(titleIndex);
2134 folderInfo.id = id;
2135 container = c.getInt(containerIndex);
2136 folderInfo.container = container;
2137 folderInfo.screenId = c.getInt(screenIndex);
2138 folderInfo.cellX = c.getInt(cellXIndex);
2139 folderInfo.cellY = c.getInt(cellYIndex);
2140 folderInfo.spanX = 1;
2141 folderInfo.spanY = 1;
2142
2143 // check & update map of what's occupied
2144 if (!checkItemPlacement(occupied, folderInfo)) {
2145 itemsToRemove.add(id);
2146 break;
2147 }
2148
2149 switch (container) {
2150 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2151 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2152 sBgWorkspaceItems.add(folderInfo);
2153 break;
2154 }
2155
2156 if (restored) {
2157 // no special handling required for restored folders
2158 restoredRows.add(id);
2159 }
2160
2161 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2162 sBgFolders.put(folderInfo.id, folderInfo);
2163 break;
2164
2165 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2166 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2167 // Read all Launcher-specific widget details
2168 boolean customWidget = itemType ==
2169 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2170
2171 int appWidgetId = c.getInt(appWidgetIdIndex);
2172 String savedProvider = c.getString(appWidgetProviderIndex);
2173 id = c.getLong(idIndex);
2174 final ComponentName component =
2175 ComponentName.unflattenFromString(savedProvider);
2176
2177 final int restoreStatus = c.getInt(restoredIndex);
2178 final boolean isIdValid = (restoreStatus &
2179 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2180
2181 final boolean wasProviderReady = (restoreStatus &
2182 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2183
2184 final LauncherAppWidgetProviderInfo provider =
2185 LauncherModel.getProviderInfo(context,
2186 ComponentName.unflattenFromString(savedProvider));
2187
2188 final boolean isProviderReady = isValidProvider(provider);
2189 if (!isSafeMode && !customWidget &&
2190 wasProviderReady && !isProviderReady) {
2191 String log = "Deleting widget that isn't installed anymore: "
2192 + "id=" + id + " appWidgetId=" + appWidgetId;
2193
2194 Log.e(TAG, log);
2195 Launcher.addDumpLog(TAG, log, false);
2196 itemsToRemove.add(id);
2197 } else {
2198 if (isProviderReady) {
2199 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2200 provider.provider);
2201
2202 if (!customWidget) {
2203 int[] minSpan =
2204 Launcher.getMinSpanForWidget(context, provider);
2205 appWidgetInfo.minSpanX = minSpan[0];
2206 appWidgetInfo.minSpanY = minSpan[1];
2207 }
2208
2209 int status = restoreStatus;
2210 if (!wasProviderReady) {
2211 // If provider was not previously ready, update the
2212 // status and UI flag.
2213
2214 // Id would be valid only if the widget restore broadcast was🔵
2215 if (isIdValid) {
2216 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2217 } else {
2218 status &= ~LauncherAppWidgetInfo
2219 .FLAG_PROVIDER_NOT_READY;
2220 }
2221 }
2222 appWidgetInfo.restoreStatus = status;
2223 } else {
2224 Log.v(TAG, "Widget restore pending id=" + id
2225 + " appWidgetId=" + appWidgetId
2226 + " status =" + restoreStatus);
2227 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2228 component);
2229 appWidgetInfo.restoreStatus = restoreStatus;
2230
2231 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) 🔵
2232 // Restore has started once.
2233 } else if (installingPkgs.contains(component.getPackageName())) {
2234 // App restore has started. Update the flag
2235 appWidgetInfo.restoreStatus |=
2236 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2237 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2238 Launcher.addDumpLog(TAG,
2239 "Unrestored widget removed: " + component, true);
2240 itemsToRemove.add(id);
2241 continue;
2242 }
2243 }
2244
2245 appWidgetInfo.id = id;
2246 appWidgetInfo.screenId = c.getInt(screenIndex);
2247 appWidgetInfo.cellX = c.getInt(cellXIndex);
2248 appWidgetInfo.cellY = c.getInt(cellYIndex);
2249 appWidgetInfo.spanX = c.getInt(spanXIndex);
2250 appWidgetInfo.spanY = c.getInt(spanYIndex);
2251
2252 if (!customWidget) {
2253 int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
2254 appWidgetInfo.minSpanX = minSpan[0];
2255 appWidgetInfo.minSpanY = minSpan[1];
2256 }
2257
2258 container = c.getInt(containerIndex);
2259 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2260 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2261 Log.e(TAG, "Widget found where container != " +
2262 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2263 continue;
2264 }
2265
2266 appWidgetInfo.container = c.getInt(containerIndex);
2267 // check & update map of what's occupied
2268 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2269 itemsToRemove.add(id);
2270 break;
2271 }
2272
2273 if (!customWidget) {
2274 String providerName =
2275 appWidgetInfo.providerName.flattenToString();
2276 if (!providerName.equals(savedProvider) ||
2277 (appWidgetInfo.restoreStatus != restoreStatus)) {
2278 ContentValues values = new ContentValues();
2279 values.put(
2280 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2281 providerName);
2282 values.put(LauncherSettings.Favorites.RESTORED,
2283 appWidgetInfo.restoreStatus);
2284 updateItem(id, values);
2285 }
2286 }
2287 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2288 sBgAppWidgets.add(appWidgetInfo);
2289 }
2290 break;
2291 }
2292 } catch (Exception e) {
2293 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2294 }
2295 }
2296 } finally {
2297 if (c != null) {
2298 c.close();
2299 }
2300 }
2301
2302 // Break early if we've stopped loading
2303 if (mStopped) {
2304 clearSBgDataStructures();
2305 return;
2306 }
2307
2308 if (itemsToRemove.size() > 0) {
2309 ContentProviderClient client = contentResolver.acquireContentProviderClient(
2310 contentUri);
2311 // Remove dead items
2312 for (long id : itemsToRemove) {
2313 if (DEBUG_LOADERS) {
2314 Log.d(TAG, "Removed id = " + id);
2315 }
2316 // Don't notify content observers
2317 try {
2318 client.delete(LauncherSettings.Favorites.getContentUri(id, false),
2319 null, null);
2320 } catch (RemoteException e) {
2321 Log.w(TAG, "Could not remove id = " + id);
2322 }
2323 }
2324 }
2325
2326 if (restoredRows.size() > 0) {
2327 ContentProviderClient updater = contentResolver.acquireContentProviderClient(
2328 contentUri);
2329 // Update restored items that no longer require special handling
2330 try {
2331 StringBuilder selectionBuilder = new StringBuilder();
2332 selectionBuilder.append(LauncherSettings.Favorites._ID);
2333 selectionBuilder.append(" IN (");
2334 selectionBuilder.append(TextUtils.join(", ", restoredRows));
2335 selectionBuilder.append(")");
2336 ContentValues values = new ContentValues();
2337 values.put(LauncherSettings.Favorites.RESTORED, 0);
2338 updater.update(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
2339 values, selectionBuilder.toString(), null);
2340 } catch (RemoteException e) {
2341 Log.w(TAG, "Could not update restored rows");
2342 }
2343 }
2344
2345 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2346 context.registerReceiver(new AppsAvailabilityCheck(),
2347 new IntentFilter(StartupReceiver.SYSTEM_READY),
2348 null, sWorker);
2349 }
2350
2351 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2352 // Log to disk
2353 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
2354 TextUtils.join(", ", sBgWorkspaceScreens), true);
2355
2356 // Remove any empty screens
2357 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2358 for (ItemInfo item: sBgItemsIdMap.values()) {
2359 long screenId = item.screenId;
2360 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2361 unusedScreens.contains(screenId)) {
2362 unusedScreens.remove(screenId);
2363 }
2364 }
2365
2366 // If there are any empty screens remove them, and update.
2367 if (unusedScreens.size() != 0) {
2368 // Log to disk
2369 Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
2370 TextUtils.join(", ", unusedScreens), true);
2371
2372 sBgWorkspaceScreens.removeAll(unusedScreens);
2373 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2374 }
2375
2376 if (DEBUG_LOADERS) {
2377 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2378 Log.d(TAG, "workspace layout: ");
2379 int nScreens = occupied.size();
2380 for (int y = 0; y < countY; y++) {
2381 String line = "";
2382
2383 Iterator<Long> iter = occupied.keySet().iterator();
2384 while (iter.hasNext()) {
2385 long screenId = iter.next();
2386 if (screenId > 0) {
2387 line += " | ";
2388 }
2389 for (int x = 0; x < countX; x++) {
2390 ItemInfo[][] screen = occupied.get(screenId);
2391 if (x < screen.length && y < screen[x].length) {
2392 line += (screen[x][y] != null) ? "#" : ".";
2393 } else {
2394 line += "!";
2395 }
2396 }
2397 }
2398 Log.d(TAG, "[ " + line + " ]");
2399 }
2400 }
2401 }
2402 }
2403
2404 /**
2405 * Partially updates the item without any notification. Must be called on the worker thread.
2406 */
2407 private void updateItem(long itemId, ContentValues update) {
2408 mContext.getContentResolver().update(
2409 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
2410 update,
2411 BaseColumns._ID + "= ?",
2412 new String[]{Long.toString(itemId)});
2413 }
2414
2415 /** Filters the set of items who are directly or indirectly (via another container) on the
2416 * specified screen. */
2417 private void filterCurrentWorkspaceItems(long currentScreenId,
2418 ArrayList<ItemInfo> allWorkspaceItems,
2419 ArrayList<ItemInfo> currentScreenItems,
2420 ArrayList<ItemInfo> otherScreenItems) {
2421 // Purge any null ItemInfos
2422 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2423 while (iter.hasNext()) {
2424 ItemInfo i = iter.next();
2425 if (i == null) {
2426 iter.remove();
2427 }
2428 }
2429
2430 // Order the set of items by their containers first, this allows use to walk through the
2431 // list sequentially, build up a list of containers that are in the specified screen,
2432 // as well as all items in those containers.
2433 Set<Long> itemsOnScreen = new HashSet<Long>();
2434 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2435 @Override
2436 public int compare(ItemInfo lhs, ItemInfo rhs) {
2437 return (int) (lhs.container - rhs.container);
2438 }
2439 });
2440 for (ItemInfo info : allWorkspaceItems) {
2441 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2442 if (info.screenId == currentScreenId) {
2443 currentScreenItems.add(info);
2444 itemsOnScreen.add(info.id);
2445 } else {
2446 otherScreenItems.add(info);
2447 }
2448 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2449 currentScreenItems.add(info);
2450 itemsOnScreen.add(info.id);
2451 } else {
2452 if (itemsOnScreen.contains(info.container)) {
2453 currentScreenItems.add(info);
2454 itemsOnScreen.add(info.id);
2455 } else {
2456 otherScreenItems.add(info);
2457 }
2458 }
2459 }
2460 }
2461
2462 /** Filters the set of widgets which are on the specified screen. */
2463 private void filterCurrentAppWidgets(long currentScreenId,
2464 ArrayList<LauncherAppWidgetInfo> appWidgets,
2465 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2466 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2467
2468 for (LauncherAppWidgetInfo widget : appWidgets) {
2469 if (widget == null) continue;
2470 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2471 widget.screenId == currentScreenId) {
2472 currentScreenWidgets.add(widget);
2473 } else {
2474 otherScreenWidgets.add(widget);
2475 }
2476 }
2477 }
2478
2479 /** Filters the set of folders which are on the specified screen. */
2480 private void filterCurrentFolders(long currentScreenId,
2481 HashMap<Long, ItemInfo> itemsIdMap,
2482 HashMap<Long, FolderInfo> folders,
2483 HashMap<Long, FolderInfo> currentScreenFolders,
2484 HashMap<Long, FolderInfo> otherScreenFolders) {
2485
2486 for (long id : folders.keySet()) {
2487 ItemInfo info = itemsIdMap.get(id);
2488 FolderInfo folder = folders.get(id);
2489 if (info == null || folder == null) continue;
2490 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2491 info.screenId == currentScreenId) {
2492 currentScreenFolders.put(id, folder);
2493 } else {
2494 otherScreenFolders.put(id, folder);
2495 }
2496 }
2497 }
2498
2499 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2500 * right) */
2501 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2502 final LauncherAppState app = LauncherAppState.getInstance();
2503 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2504 // XXX: review this
2505 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2506 @Override
2507 public int compare(ItemInfo lhs, ItemInfo rhs) {
2508 int cellCountX = (int) grid.numColumns;
2509 int cellCountY = (int) grid.numRows;
2510 int screenOffset = cellCountX * cellCountY;
2511 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2512 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2513 lhs.cellY * cellCountX + lhs.cellX);
2514 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2515 rhs.cellY * cellCountX + rhs.cellX);
2516 return (int) (lr - rr);
2517 }
2518 });
2519 }
2520
2521 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2522 final ArrayList<Long> orderedScreens) {
2523 final Runnable r = new Runnable() {
2524 @Override
2525 public void run() {
2526 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2527 if (callbacks != null) {
2528 callbacks.bindScreens(orderedScreens);
2529 }
2530 }
2531 };
2532 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2533 }
2534
2535 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2536 final ArrayList<ItemInfo> workspaceItems,
2537 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2538 final HashMap<Long, FolderInfo> folders,
2539 ArrayList<Runnable> deferredBindRunnables) {
2540
2541 final boolean postOnMainThread = (deferredBindRunnables != null);
2542
2543 // Bind the workspace items
2544 int N = workspaceItems.size();
2545 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2546 final int start = i;
2547 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2548 final Runnable r = new Runnable() {
2549 @Override
2550 public void run() {
2551 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2552 if (callbacks != null) {
2553 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2554 false);
2555 }
2556 }
2557 };
2558 if (postOnMainThread) {
2559 synchronized (deferredBindRunnables) {
2560 deferredBindRunnables.add(r);
2561 }
2562 } else {
2563 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2564 }
2565 }
2566
2567 // Bind the folders
2568 if (!folders.isEmpty()) {
2569 final Runnable r = new Runnable() {
2570 public void run() {
2571 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2572 if (callbacks != null) {
2573 callbacks.bindFolders(folders);
2574 }
2575 }
2576 };
2577 if (postOnMainThread) {
2578 synchronized (deferredBindRunnables) {
2579 deferredBindRunnables.add(r);
2580 }
2581 } else {
2582 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2583 }
2584 }
2585
2586 // Bind the widgets, one at a time
2587 N = appWidgets.size();
2588 for (int i = 0; i < N; i++) {
2589 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2590 final Runnable r = new Runnable() {
2591 public void run() {
2592 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2593 if (callbacks != null) {
2594 callbacks.bindAppWidget(widget);
2595 }
2596 }
2597 };
2598 if (postOnMainThread) {
2599 deferredBindRunnables.add(r);
2600 } else {
2601 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2602 }
2603 }
2604 }
2605
2606 /**
2607 * Binds all loaded data to actual views on the main thread.
2608 */
2609 private void bindWorkspace(int synchronizeBindPage) {
2610 final long t = SystemClock.uptimeMillis();
2611 Runnable r;
2612
2613 // Don't use these two variables in any of the callback runnables.
2614 // Otherwise we hold a reference to them.
2615 final Callbacks oldCallbacks = mCallbacks.get();
2616 if (oldCallbacks == null) {
2617 // This launcher has exited and nobody bothered to tell us. Just bail.
2618 Log.w(TAG, "LoaderTask running with no launcher");
2619 return;
2620 }
2621
2622 // Save a copy of all the bg-thread collections
2623 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2624 ArrayList<LauncherAppWidgetInfo> appWidgets =
2625 new ArrayList<LauncherAppWidgetInfo>();
2626 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
2627 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
2628 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2629 synchronized (sBgLock) {
2630 workspaceItems.addAll(sBgWorkspaceItems);
2631 appWidgets.addAll(sBgAppWidgets);
2632 folders.putAll(sBgFolders);
2633 itemsIdMap.putAll(sBgItemsIdMap);
2634 orderedScreenIds.addAll(sBgWorkspaceScreens);
2635 }
2636
2637 final boolean isLoadingSynchronously =
2638 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2639 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2640 oldCallbacks.getCurrentWorkspaceScreen();
2641 if (currScreen >= orderedScreenIds.size()) {
2642 // There may be no workspace screens (just hotseat items and an empty page).
2643 currScreen = PagedView.INVALID_RESTORE_PAGE;
2644 }
2645 final int currentScreen = currScreen;
2646 final long currentScreenId = currentScreen < 0
2647 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2648
2649 // Load all the items that are on the current page first (and in the process, unbind
2650 // all the existing workspace items before we call startBinding() below.
2651 unbindWorkspaceItemsOnMainThread();
2652
2653 // Separate the items that are on the current screen, and all the other remaining items
2654 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2655 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2656 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2657 new ArrayList<LauncherAppWidgetInfo>();
2658 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2659 new ArrayList<LauncherAppWidgetInfo>();
2660 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
2661 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
2662
2663 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2664 otherWorkspaceItems);
2665 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2666 otherAppWidgets);
2667 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2668 otherFolders);
2669 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2670 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2671
2672 // Tell the workspace that we're about to start binding items
2673 r = new Runnable() {
2674 public void run() {
2675 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2676 if (callbacks != null) {
2677 callbacks.startBinding();
2678 }
2679 }
2680 };
2681 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2682
2683 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2684
2685 // Load items on the current page
2686 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2687 currentFolders, null);
2688 if (isLoadingSynchronously) {
2689 r = new Runnable() {
2690 public void run() {
2691 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2692 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2693 callbacks.onPageBoundSynchronously(currentScreen);
2694 }
2695 }
2696 };
2697 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2698 }
2699
2700 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2701 // work until after the first render)
2702 synchronized (mDeferredBindRunnables) {
2703 mDeferredBindRunnables.clear();
2704 }
2705 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2706 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2707
2708 // Tell the workspace that we're done binding items
2709 r = new Runnable() {
2710 public void run() {
2711 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2712 if (callbacks != null) {
2713 callbacks.finishBindingItems();
2714 }
2715
2716 // If we're profiling, ensure this is the last thing in the queue.
2717 if (DEBUG_LOADERS) {
2718 Log.d(TAG, "bound workspace in "
2719 + (SystemClock.uptimeMillis()-t) + "ms");
2720 }
2721
2722 mIsLoadingAndBindingWorkspace = false;
2723 }
2724 };
2725 if (isLoadingSynchronously) {
2726 synchronized (mDeferredBindRunnables) {
2727 mDeferredBindRunnables.add(r);
2728 }
2729 } else {
2730 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2731 }
2732 }
2733
2734 private void loadAndBindAllApps() {
2735 if (DEBUG_LOADERS) {
2736 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2737 }
2738 if (!mAllAppsLoaded) {
2739 loadAllApps();
2740 synchronized (LoaderTask.this) {
2741 if (mStopped) {
2742 return;
2743 }
2744 mAllAppsLoaded = true;
2745 }
2746 } else {
2747 onlyBindAllApps();
2748 }
2749 }
2750
2751 private void onlyBindAllApps() {
2752 final Callbacks oldCallbacks = mCallbacks.get();
2753 if (oldCallbacks == null) {
2754 // This launcher has exited and nobody bothered to tell us. Just bail.
2755 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2756 return;
2757 }
2758
2759 // shallow copy
2760 @SuppressWarnings("unchecked")
2761 final ArrayList<AppInfo> list
2762 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2763 Runnable r = new Runnable() {
2764 public void run() {
2765 final long t = SystemClock.uptimeMillis();
2766 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2767 if (callbacks != null) {
2768 callbacks.bindAllApplications(list);
2769 }
2770 if (DEBUG_LOADERS) {
2771 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2772 + (SystemClock.uptimeMillis()-t) + "ms");
2773 }
2774 }
2775 };
2776 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2777 if (isRunningOnMainThread) {
2778 r.run();
2779 } else {
2780 mHandler.post(r);
2781 }
2782 }
2783
2784 private void loadAllApps() {
2785 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2786
2787 final Callbacks oldCallbacks = mCallbacks.get();
2788 if (oldCallbacks == null) {
2789 // This launcher has exited and nobody bothered to tell us. Just bail.
2790 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2791 return;
2792 }
2793
2794 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
2795 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2796
2797 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2798
2799 // Clear the list of apps
2800 mBgAllAppsList.clear();
2801 SharedPreferences prefs = mContext.getSharedPreferences(
2802 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
2803 for (UserHandleCompat user : profiles) {
2804 // Query for the set of apps
2805 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2806 List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2807 if (DEBUG_LOADERS) {
2808 Log.d(TAG, "getActivityList took "
2809 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2810 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2811 }
2812 // Fail if we don't have any apps
2813 // TODO: Fix this. Only fail for the current user.
2814 if (apps == null || apps.isEmpty()) {
2815 return;
2816 }
2817
2818 // Update icon cache
2819 HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
2820
2821 // If any package icon has changed (app was updated while launcher was dead),
2822 // update the corresponding shortcuts.
2823 if (!updatedPackages.isEmpty()) {
2824 final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
2825 synchronized (sBgLock) {
2826 for (ItemInfo info : sBgItemsIdMap.values()) {
2827 if (info instanceof ShortcutInfo && user.equals(info.user)
2828 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION)🔵
2829 ShortcutInfo si = (ShortcutInfo) info;
2830 ComponentName cn = si.getTargetComponent();
2831 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2832 si.updateIcon(mIconCache);
2833 updates.add(si);
2834 }
2835 }
2836 }
2837 }
2838
2839 if (!updates.isEmpty()) {
2840 final UserHandleCompat userFinal = user;
2841 mHandler.post(new Runnable() {
2842
2843 public void run() {
2844 Callbacks cb = getCallback();
2845 if (cb != null) {
2846 cb.bindShortcutsChanged(
2847 updates, new ArrayList<ShortcutInfo>(), userFinal);
2848 }
2849 }
2850 });
2851 }
2852 }
2853
2854 // Create the ApplicationInfos
2855 for (int i = 0; i < apps.size(); i++) {
2856 LauncherActivityInfoCompat app = apps.get(i);
2857 // This builds the icon bitmaps.
2858 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2859 }
2860
2861 if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
2862 // Add shortcuts for packages which were installed while launcher was dead.
2863 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
2864 + mUserManager.getSerialNumberForUser(user);
2865 Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET🔵
2866 HashSet<String> newPackageSet = new HashSet<String>();
2867
2868 for (LauncherActivityInfoCompat info : apps) {
2869 String packageName = info.getComponentName().getPackageName();
2870 if (!packagesAdded.contains(packageName)
2871 && !newPackageSet.contains(packageName)) {
2872 InstallShortcutReceiver.queueInstallShortcut(info, mContext);
2873 }
2874 newPackageSet.add(packageName);
2875 }
2876
2877 prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
2878 }
2879 }
2880 // Huh? Shouldn't this be inside the Runnable below?
2881 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2882 mBgAllAppsList.added = new ArrayList<AppInfo>();
2883
2884 // Post callback on main thread
2885 mHandler.post(new Runnable() {
2886 public void run() {
2887 final long bindTime = SystemClock.uptimeMillis();
2888 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2889 if (callbacks != null) {
2890 callbacks.bindAllApplications(added);
2891 if (DEBUG_LOADERS) {
2892 Log.d(TAG, "bound " + added.size() + " apps in "
2893 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2894 }
2895 } else {
2896 Log.i(TAG, "not binding apps: no Launcher activity");
2897 }
2898 }
2899 });
2900
2901 if (DEBUG_LOADERS) {
2902 Log.d(TAG, "Icons processed in "
2903 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2904 }
2905 }
2906
2907 public void dumpState() {
2908 synchronized (sBgLock) {
2909 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2910 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
2911 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2912 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2913 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2914 }
2915 }
2916 }
2917
2918 void enqueuePackageUpdated(PackageUpdatedTask task) {
2919 sWorker.post(task);
2920 }
2921
2922 private class AppsAvailabilityCheck extends BroadcastReceiver {
2923
2924 @Override
2925 public void onReceive(Context context, Intent intent) {
2926 synchronized (sBgLock) {
2927 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2928 .getInstance(mApp.getContext());
2929 final PackageManager manager = context.getPackageManager();
2930 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2931 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2932 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2933 UserHandleCompat user = entry.getKey();
2934 packagesRemoved.clear();
2935 packagesUnavailable.clear();
2936 for (String pkg : entry.getValue()) {
2937 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2938 boolean packageOnSdcard = launcherApps.isAppEnabled(
2939 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
2940 if (packageOnSdcard) {
2941 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2942 packagesUnavailable.add(pkg);
2943 } else {
2944 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2945 packagesRemoved.add(pkg);
2946 }
2947 }
2948 }
2949 if (!packagesRemoved.isEmpty()) {
2950 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2951 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2952 }
2953 if (!packagesUnavailable.isEmpty()) {
2954 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2955 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
2956 }
2957 }
2958 sPendingPackages.clear();
2959 }
2960 }
2961 }
2962
2963 private class PackageUpdatedTask implements Runnable {
2964 int mOp;
2965 String[] mPackages;
2966 UserHandleCompat mUser;
2967
2968 public static final int OP_NONE = 0;
2969 public static final int OP_ADD = 1;
2970 public static final int OP_UPDATE = 2;
2971 public static final int OP_REMOVE = 3; // uninstlled
2972 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2973
2974
2975 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2976 mOp = op;
2977 mPackages = packages;
2978 mUser = user;
2979 }
2980
2981 public void run() {
2982 final Context context = mApp.getContext();
2983
2984 final String[] packages = mPackages;
2985 final int N = packages.length;
2986 switch (mOp) {
2987 case OP_ADD:
2988 for (int i=0; i<N; i++) {
2989 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2990 mIconCache.updateIconsForPkg(packages[i], mUser);
2991 mBgAllAppsList.addPackage(context, packages[i], mUser);
2992 }
2993
2994 // Auto add shortcuts for added packages.
2995 if (ADD_MANAGED_PROFILE_SHORTCUTS
2996 && !UserHandleCompat.myUserHandle().equals(mUser)) {
2997 SharedPreferences prefs = context.getSharedPreferences(
2998 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
2999 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
3000 + mUserManager.getSerialNumberForUser(mUser);
3001 Set<String> shortcutSet = new HashSet<String>(
3002 prefs.getStringSet(shortcutsSetKey,Collections.EMPTY_SET));
3003
3004 for (int i=0; i<N; i++) {
3005 if (!shortcutSet.contains(packages[i])) {
3006 shortcutSet.add(packages[i]);
3007 List<LauncherActivityInfoCompat> activities =
3008 mLauncherApps.getActivityList(packages[i], mUser);
3009 if (activities != null && !activities.isEmpty()) {
3010 InstallShortcutReceiver.queueInstallShortcut(
3011 activities.get(0), context);
3012 }
3013 }
3014 }
3015
3016 prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
3017 }
3018 break;
3019 case OP_UPDATE:
3020 for (int i=0; i<N; i++) {
3021 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3022 mIconCache.updateIconsForPkg(packages[i], mUser);
3023 mBgAllAppsList.updatePackage(context, packages[i], mUser);
3024 WidgetPreviewLoader.removePackageFromDb(
3025 mApp.getWidgetPreviewCacheDb(), packages[i]);
3026 }
3027 break;
3028 case OP_REMOVE:
3029 // Remove the packageName for the set of auto-installed shortcuts. This
3030 // will ensure that the shortcut when the app is installed again.
3031 if (ADD_MANAGED_PROFILE_SHORTCUTS
3032 && !UserHandleCompat.myUserHandle().equals(mUser)) {
3033 SharedPreferences prefs = context.getSharedPreferences(
3034 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
3035 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
3036 + mUserManager.getSerialNumberForUser(mUser);
3037 HashSet<String> shortcutSet = new HashSet<String>(
3038 prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET));
3039 shortcutSet.removeAll(Arrays.asList(mPackages));
3040 prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
3041 }
3042 for (int i=0; i<N; i++) {
3043 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3044 mIconCache.removeIconsForPkg(packages[i], mUser);
3045 }
3046 // Fall through
3047 case OP_UNAVAILABLE:
3048 for (int i=0; i<N; i++) {
3049 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3050 mBgAllAppsList.removePackage(packages[i], mUser);
3051 WidgetPreviewLoader.removePackageFromDb(
3052 mApp.getWidgetPreviewCacheDb(), packages[i]);
3053 }
3054 break;
3055 }
3056
3057 ArrayList<AppInfo> added = null;
3058 ArrayList<AppInfo> modified = null;
3059 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3060
3061 if (mBgAllAppsList.added.size() > 0) {
3062 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
3063 mBgAllAppsList.added.clear();
3064 }
3065 if (mBgAllAppsList.modified.size() > 0) {
3066 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
3067 mBgAllAppsList.modified.clear();
3068 }
3069 if (mBgAllAppsList.removed.size() > 0) {
3070 removedApps.addAll(mBgAllAppsList.removed);
3071 mBgAllAppsList.removed.clear();
3072 }
3073
3074 final Callbacks callbacks = getCallback();
3075 if (callbacks == null) {
3076 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
3077 return;
3078 }
3079
3080 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
3081 new HashMap<ComponentName, AppInfo>();
3082
3083 if (added != null) {
3084 addAppsToAllApps(context, added);
3085 for (AppInfo ai : added) {
3086 addedOrUpdatedApps.put(ai.componentName, ai);
3087 }
3088 }
3089
3090 if (modified != null) {
3091 final ArrayList<AppInfo> modifiedFinal = modified;
3092 for (AppInfo ai : modified) {
3093 addedOrUpdatedApps.put(ai.componentName, ai);
3094 }
3095
3096 mHandler.post(new Runnable() {
3097 public void run() {
3098 Callbacks cb = getCallback();
3099 if (callbacks == cb && cb != null) {
3100 callbacks.bindAppsUpdated(modifiedFinal);
3101 }
3102 }
3103 });
3104 }
3105
3106 // Update shortcut infos
3107 if (mOp == OP_ADD || mOp == OP_UPDATE) {
3108 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3109 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3110 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3111
3112 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
3113 synchronized (sBgLock) {
3114 for (ItemInfo info : sBgItemsIdMap.values()) {
3115 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3116 ShortcutInfo si = (ShortcutInfo) info;
3117 boolean infoUpdated = false;
3118 boolean shortcutUpdated = false;
3119
3120 // Update shortcuts which use iconResource.
3121 if ((si.iconResource != null)
3122 && packageSet.contains(si.iconResource.packageName)) {
3123 Bitmap icon = Utilities.createIconBitmap(si.iconResource.packageName,
3124 si.iconResource.resourceName, mIconCache, context);
3125 if (icon != null) {
3126 si.setIcon(icon);
3127 si.usingFallbackIcon = false;
3128 infoUpdated = true;
3129 }
3130 }
3131
3132 ComponentName cn = si.getTargetComponent();
3133 if (cn != null && packageSet.contains(cn.getPackageName())) {
3134 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3135
3136 if (si.isPromise()) {
3137 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3138 // Auto install icon
3139 PackageManager pm = context.getPackageManager();
3140 ResolveInfo matched = pm.resolveActivity(
3141 new Intent(Intent.ACTION_MAIN)
3142 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3143 PackageManager.MATCH_DEFAULT_ONLY);
3144 if (matched == null) {
3145 // Try to find the best match activity.
3146 Intent intent = pm.getLaunchIntentForPackage(
3147 cn.getPackageName());
3148 if (intent != null) {
3149 cn = intent.getComponent();
3150 appInfo = addedOrUpdatedApps.get(cn);
3151 }
3152
3153 if ((intent == null) || (appInfo == null)) {
3154 removedShortcuts.add(si);
3155 continue;
3156 }
3157 si.promisedIntent = intent;
3158 }
3159 }
3160
3161 // Restore the shortcut.
3162 si.intent = si.promisedIntent;
3163 si.promisedIntent = null;
3164 si.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
3165 & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
3166 & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
3167
3168 infoUpdated = true;
3169 si.updateIcon(mIconCache);
3170 }
3171
3172 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3173 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATIO🔵
3174 si.updateIcon(mIconCache);
3175 si.title = appInfo.title.toString();
3176 si.contentDescription = appInfo.contentDescription;
3177 infoUpdated = true;
3178 }
3179
3180 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3181 // Since package was just updated, the target must be available now.
3182 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3183 shortcutUpdated = true;
3184 }
3185 }
3186
3187 if (infoUpdated || shortcutUpdated) {
3188 updatedShortcuts.add(si);
3189 }
3190 if (infoUpdated) {
3191 updateItemInDatabase(context, si);
3192 }
3193 } else if (info instanceof LauncherAppWidgetInfo) {
3194 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3195 if (mUser.equals(widgetInfo.user)
3196 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_🔵
3197 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
3198 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3199 widgets.add(widgetInfo);
3200 updateItemInDatabase(context, widgetInfo);
3201 }
3202 }
3203 }
3204 }
3205
3206 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
3207 mHandler.post(new Runnable() {
3208
3209 public void run() {
3210 Callbacks cb = getCallback();
3211 if (callbacks == cb && cb != null) {
3212 callbacks.bindShortcutsChanged(
3213 updatedShortcuts, removedShortcuts, mUser);
3214 }
3215 }
3216 });
3217 if (!removedShortcuts.isEmpty()) {
3218 deleteItemsFromDatabase(context, removedShortcuts);
3219 }
3220 }
3221 if (!widgets.isEmpty()) {
3222 mHandler.post(new Runnable() {
3223 public void run() {
3224 Callbacks cb = getCallback();
3225 if (callbacks == cb && cb != null) {
3226 callbacks.bindWidgetsRestored(widgets);
3227 }
3228 }
3229 });
3230 }
3231 }
3232
3233 final ArrayList<String> removedPackageNames =
3234 new ArrayList<String>();
3235 if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
3236 // Mark all packages in the broadcast to be removed
3237 removedPackageNames.addAll(Arrays.asList(packages));
3238 } else if (mOp == OP_UPDATE) {
3239 // Mark disabled packages in the broadcast to be removed
3240 for (int i=0; i<N; i++) {
3241 if (isPackageDisabled(context, packages[i], mUser)) {
3242 removedPackageNames.add(packages[i]);
3243 }
3244 }
3245 }
3246
3247 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3248 final int removeReason;
3249 if (mOp == OP_UNAVAILABLE) {
3250 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3251 } else {
3252 // Remove all the components associated with this package
3253 for (String pn : removedPackageNames) {
3254 deletePackageFromDatabase(context, pn, mUser);
3255 }
3256 // Remove all the specific components
3257 for (AppInfo a : removedApps) {
3258 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3259 deleteItemsFromDatabase(context, infos);
3260 }
3261 removeReason = 0;
3262 }
3263
3264 // Remove any queued items from the install queue
3265 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3266 // Call the components-removed callback
3267 mHandler.post(new Runnable() {
3268 public void run() {
3269 Callbacks cb = getCallback();
3270 if (callbacks == cb && cb != null) {
3271 callbacks.bindComponentsRemoved(
3272 removedPackageNames, removedApps, mUser, removeReason);
3273 }
3274 }
3275 });
3276 }
3277
3278 final ArrayList<Object> widgetsAndShortcuts =
3279 getSortedWidgetsAndShortcuts(context);
3280 mHandler.post(new Runnable() {
3281 @Override
3282 public void run() {
3283 Callbacks cb = getCallback();
3284 if (callbacks == cb && cb != null) {
3285 callbacks.bindPackagesUpdated(widgetsAndShortcuts);
3286 }
3287 }
3288 });
3289
3290 // Write all the logs to disk
3291 mHandler.post(new Runnable() {
3292 public void run() {
3293 Callbacks cb = getCallback();
3294 if (callbacks == cb && cb != null) {
3295 callbacks.dumpLogsToLocalData();
3296 }
3297 }
3298 });
3299 }
3300 }
3301
3302 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context) {
3303 synchronized (sBgLock) {
3304 if (sBgWidgetProviders != null && !sWidgetProvidersDirty) {
3305 return new ArrayList<LauncherAppWidgetProviderInfo>(sBgWidgetProviders.values());
3306 }
3307 sBgWidgetProviders = new HashMap<ComponentName, LauncherAppWidgetProviderInfo>();
3308 List<AppWidgetProviderInfo> widgets =
3309 AppWidgetManagerCompat.getInstance(context).getAllProviders();
3310 LauncherAppWidgetProviderInfo info;
3311 for (AppWidgetProviderInfo pInfo : widgets) {
3312 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3313 sBgWidgetProviders.put(info.provider, info);
3314 }
3315
3316 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3317 for (CustomAppWidget widget : customWidgets) {
3318 info = new LauncherAppWidgetProviderInfo(context, widget);
3319 sBgWidgetProviders.put(info.provider, info);
3320 }
3321 sWidgetProvidersDirty = false;
3322 return new ArrayList<LauncherAppWidgetProviderInfo>(sBgWidgetProviders.values());
3323 }
3324 }
3325
3326 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name) {
3327 synchronized (sBgLock) {
3328 if (sBgWidgetProviders == null) {
3329 getWidgetProviders(ctx);
3330 }
3331 return sBgWidgetProviders.get(name);
3332 }
3333 }
3334
3335 // Returns a list of ResolveInfos/AppWindowInfos in sorted order
3336 public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
3337 PackageManager packageManager = context.getPackageManager();
3338 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3339 widgetsAndShortcuts.addAll(getWidgetProviders(context));
3340 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3341 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3342 Collections.sort(widgetsAndShortcuts, new WidgetAndShortcutNameComparator(context));
3343 return widgetsAndShortcuts;
3344 }
3345
3346 private static boolean isPackageDisabled(Context context, String packageName,
3347 UserHandleCompat user) {
3348 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3349 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3350 }
3351
3352 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3353 UserHandleCompat user) {
3354 if (cn == null) {
3355 return false;
3356 }
3357 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3358 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3359 return false;
3360 }
3361 return launcherApps.isActivityEnabledForProfile(cn, user);
3362 }
3363
3364 public static boolean isValidPackage(Context context, String packageName,
3365 UserHandleCompat user) {
3366 if (packageName == null) {
3367 return false;
3368 }
3369 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3370 return launcherApps.isPackageEnabledForProfile(packageName, user);
3371 }
3372
3373 /**
3374 * Make an ShortcutInfo object for a restored application or shortcut item that points
3375 * to a package that is not yet installed on the system.
3376 */
3377 public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent,
3378 int promiseType) {
3379 final ShortcutInfo info = new ShortcutInfo();
3380 info.user = UserHandleCompat.myUserHandle();
3381 mIconCache.getTitleAndIcon(info, intent, info.user);
3382
3383 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3384 String title = (cursor != null) ? cursor.getString(titleIndex) : null;
3385 if (!TextUtils.isEmpty(title)) {
3386 info.title = title;
3387 }
3388 info.status = ShortcutInfo.FLAG_RESTORED_ICON;
3389 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3390 if (TextUtils.isEmpty(info.title)) {
3391 info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
3392 }
3393 info.status = ShortcutInfo.FLAG_AUTOINTALL_ICON;
3394 } else {
3395 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3396 }
3397
3398 info.contentDescription = mUserManager.getBadgedLabelForUser(
3399 info.title.toString(), info.user);
3400 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3401 info.promisedIntent = intent;
3402 return info;
3403 }
3404
3405 /**
3406 * Make an Intent object for a restored application or shortcut item that points
3407 * to the market page for the item.
3408 */
3409 private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3410 ComponentName componentName = intent.getComponent();
3411 return getMarketIntent(componentName.getPackageName());
3412 }
3413
3414 static Intent getMarketIntent(String packageName) {
3415 return new Intent(Intent.ACTION_VIEW)
3416 .setData(new Uri.Builder()
3417 .scheme("market")
3418 .authority("details")
3419 .appendQueryParameter("id", packageName)
3420 .build());
3421 }
3422
3423 /**
3424 * Make an ShortcutInfo object for a shortcut that is an application.
3425 *
3426 * If c is not null, then it will be used to fill in missing data like the title and icon.
3427 */
3428 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
3429 UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
3430 boolean allowMissingTarget) {
3431 if (user == null) {
3432 Log.d(TAG, "Null user found in getShortcutInfo");
3433 return null;
3434 }
3435
3436 ComponentName componentName = intent.getComponent();
3437 if (componentName == null) {
3438 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3439 return null;
3440 }
3441
3442 Intent newIntent = new Intent(intent.getAction(), null);
3443 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3444 newIntent.setComponent(componentName);
3445 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3446 if ((lai == null) && !allowMissingTarget) {
3447 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3448 return null;
3449 }
3450
3451 final ShortcutInfo info = new ShortcutInfo();
3452 mIconCache.getTitleAndIcon(info, componentName, lai, user, false);
3453 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3454 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3455 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3456 }
3457
3458 // from the db
3459 if (TextUtils.isEmpty(info.title) && c != null) {
3460 info.title = c.getString(titleIndex);
3461 }
3462
3463 // fall back to the class name of the activity
3464 if (info.title == null) {
3465 info.title = componentName.getClassName();
3466 }
3467
3468 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3469 info.user = user;
3470 info.contentDescription = mUserManager.getBadgedLabelForUser(
3471 info.title.toString(), info.user);
3472 return info;
3473 }
3474
3475 static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
3476 ItemInfoFilter f) {
3477 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3478 for (ItemInfo i : infos) {
3479 if (i instanceof ShortcutInfo) {
3480 ShortcutInfo info = (ShortcutInfo) i;
3481 ComponentName cn = info.getTargetComponent();
3482 if (cn != null && f.filterItem(null, info, cn)) {
3483 filtered.add(info);
3484 }
3485 } else if (i instanceof FolderInfo) {
3486 FolderInfo info = (FolderInfo) i;
3487 for (ShortcutInfo s : info.contents) {
3488 ComponentName cn = s.getTargetComponent();
3489 if (cn != null && f.filterItem(info, s, cn)) {
3490 filtered.add(s);
3491 }
3492 }
3493 } else if (i instanceof LauncherAppWidgetInfo) {
3494 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3495 ComponentName cn = info.providerName;
3496 if (cn != null && f.filterItem(null, info, cn)) {
3497 filtered.add(info);
3498 }
3499 }
3500 }
3501 return new ArrayList<ItemInfo>(filtered);
3502 }
3503
3504 private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3505 final UserHandleCompat user) {
3506 ItemInfoFilter filter = new ItemInfoFilter() {
3507 @Override
3508 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3509 if (info.user == null) {
3510 return cn.equals(cname);
3511 } else {
3512 return cn.equals(cname) && info.user.equals(user);
3513 }
3514 }
3515 };
3516 return filterItemInfos(sBgItemsIdMap.values(), filter);
3517 }
3518
3519 /**
3520 * Make an ShortcutInfo object for a shortcut that isn't an application.
3521 */
3522 private ShortcutInfo getShortcutInfo(Cursor c, Context context,
3523 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
3524 int titleIndex) {
3525
3526 Bitmap icon = null;
3527 final ShortcutInfo info = new ShortcutInfo();
3528 // Non-app shortcuts are only supported for current user.
3529 info.user = UserHandleCompat.myUserHandle();
3530 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3531
3532 // TODO: If there's an explicit component and we can't install that, delete it.
3533
3534 info.title = c.getString(titleIndex);
3535
3536 int iconType = c.getInt(iconTypeIndex);
3537 switch (iconType) {
3538 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
3539 String packageName = c.getString(iconPackageIndex);
3540 String resourceName = c.getString(iconResourceIndex);
3541 info.customIcon = false;
3542 // the resource
3543 icon = Utilities.createIconBitmap(packageName, resourceName, mIconCache, context);
3544 // the db
3545 if (icon == null) {
3546 icon = Utilities.createIconBitmap(c, iconIndex, context);
3547 }
3548 // the fallback icon
3549 if (icon == null) {
3550 icon = mIconCache.getDefaultIcon(info.user);
3551 info.usingFallbackIcon = true;
3552 }
3553 break;
3554 case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
3555 icon = Utilities.createIconBitmap(c, iconIndex, context);
3556 if (icon == null) {
3557 icon = mIconCache.getDefaultIcon(info.user);
3558 info.customIcon = false;
3559 info.usingFallbackIcon = true;
3560 } else {
3561 info.customIcon = true;
3562 }
3563 break;
3564 default:
3565 icon = mIconCache.getDefaultIcon(info.user);
3566 info.usingFallbackIcon = true;
3567 info.customIcon = false;
3568 break;
3569 }
3570 info.setIcon(icon);
3571 return info;
3572 }
3573
3574 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3575 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3576 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3577 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3578
3579 if (intent == null) {
3580 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3581 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3582 return null;
3583 }
3584
3585 Bitmap icon = null;
3586 boolean customIcon = false;
3587 ShortcutIconResource iconResource = null;
3588
3589 if (bitmap instanceof Bitmap) {
3590 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3591 customIcon = true;
3592 } else {
3593 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3594 if (extra instanceof ShortcutIconResource) {
3595 iconResource = (ShortcutIconResource) extra;
3596 icon = Utilities.createIconBitmap(iconResource.packageName,
3597 iconResource.resourceName, mIconCache, context);
3598 }
3599 }
3600
3601 final ShortcutInfo info = new ShortcutInfo();
3602
3603 // Only support intents for current user for now. Intents sent from other
3604 // users wouldn't get here without intent forwarding anyway.
3605 info.user = UserHandleCompat.myUserHandle();
3606 if (icon == null) {
3607 icon = mIconCache.getDefaultIcon(info.user);
3608 info.usingFallbackIcon = true;
3609 }
3610 info.setIcon(icon);
3611
3612 info.title = name;
3613 info.contentDescription = mUserManager.getBadgedLabelForUser(
3614 info.title.toString(), info.user);
3615 info.intent = intent;
3616 info.customIcon = customIcon;
3617 info.iconResource = iconResource;
3618
3619 return info;
3620 }
3621
3622 /**
3623 * Return an existing FolderInfo object if we have encountered this ID previously,
3624 * or make a new one.
3625 */
3626 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
3627 // See if a placeholder was created for us already
3628 FolderInfo folderInfo = folders.get(id);
3629 if (folderInfo == null) {
3630 // No placeholder -- create a new instance
3631 folderInfo = new FolderInfo();
3632 folders.put(id, folderInfo);
3633 }
3634 return folderInfo;
3635 }
3636
3637 public static final Comparator<AppInfo> getAppNameComparator() {
3638 final Collator collator = Collator.getInstance();
3639 return new Comparator<AppInfo>() {
3640 public final int compare(AppInfo a, AppInfo b) {
3641 if (a.user.equals(b.user)) {
3642 int result = collator.compare(a.title.toString().trim(),
3643 b.title.toString().trim());
3644 if (result == 0) {
3645 result = a.componentName.compareTo(b.componentName);
3646 }
3647 return result;
3648 } else {
3649 // TODO Need to figure out rules for sorting
3650 // profiles, this puts work second.
3651 return a.user.toString().compareTo(b.user.toString());
3652 }
3653 }
3654 };
3655 }
3656 public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
3657 = new Comparator<AppInfo>() {
3658 public final int compare(AppInfo a, AppInfo b) {
3659 if (a.firstInstallTime < b.firstInstallTime) return 1;
3660 if (a.firstInstallTime > b.firstInstallTime) return -1;
3661 return 0;
3662 }
3663 };
3664 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
3665 if (info.activityInfo != null) {
3666 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
3667 } else {
3668 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
3669 }
3670 }
3671
3672 public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
3673 private final AppWidgetManagerCompat mManager;
3674 private final PackageManager mPackageManager;
3675 private final HashMap<Object, String> mLabelCache;
3676 private final Collator mCollator;
3677
3678 WidgetAndShortcutNameComparator(Context context) {
3679 mManager = AppWidgetManagerCompat.getInstance(context);
3680 mPackageManager = context.getPackageManager();
3681 mLabelCache = new HashMap<Object, String>();
3682 mCollator = Collator.getInstance();
3683 }
3684 public final int compare(Object a, Object b) {
3685 String labelA, labelB;
3686 if (mLabelCache.containsKey(a)) {
3687 labelA = mLabelCache.get(a);
3688 } else {
3689 labelA = (a instanceof LauncherAppWidgetProviderInfo)
3690 ? mManager.loadLabel((LauncherAppWidgetProviderInfo) a)
3691 : ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
3692 mLabelCache.put(a, labelA);
3693 }
3694 if (mLabelCache.containsKey(b)) {
3695 labelB = mLabelCache.get(b);
3696 } else {
3697 labelB = (b instanceof LauncherAppWidgetProviderInfo)
3698 ? mManager.loadLabel((LauncherAppWidgetProviderInfo) b)
3699 : ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
3700 mLabelCache.put(b, labelB);
3701 }
3702 return mCollator.compare(labelA, labelB);
3703 }
3704 }
3705
3706 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3707 return (provider != null) && (provider.provider != null)
3708 && (provider.provider.getPackageName() != null);
3709 }
3710
3711 public void dumpState() {
3712 Log.d(TAG, "mCallbacks=" + mCallbacks);
3713 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3714 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3715 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3716 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3717 if (mLoaderTask != null) {
3718 mLoaderTask.dumpState();
3719 } else {
3720 Log.d(TAG, "mLoaderTask=null");
3721 }
3722 }
3723
3724 public Callbacks getCallback() {
3725 return mCallbacks != null ? mCallbacks.get() : null;
3726 }
3727 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.app.SearchManager;
19 import android.appwidget.AppWidgetProviderInfo;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.ContentProviderClient;
23 import android.content.ContentProviderOperation;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent.ShortcutIconResource;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.SharedPreferences;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ProviderInfo;
33 import android.content.pm.ResolveInfo;
34 import android.content.res.Configuration;
35 import android.content.res.Resources;
36 import android.database.Cursor;
37 import android.graphics.Bitmap;
38 import android.graphics.Rect;
39 import android.net.Uri;
40 import android.os.Environment;
41 import android.os.Handler;
42 import android.os.HandlerThread;
43 import android.os.Parcelable;
44 import android.os.Process;
45 import android.os.RemoteException;
46 import android.os.SystemClock;
47 import android.provider.BaseColumns;
48 import android.text.TextUtils;
49 import android.util.Log;
50 import android.util.LongSparseArray;
51 import android.util.Pair;
52 import com.android.launcher3.compat.AppWidgetManagerCompat;
53 import com.android.launcher3.compat.LauncherActivityInfoCompat;
54 import com.android.launcher3.compat.LauncherAppsCompat;
55 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
56 import com.android.launcher3.compat.PackageInstallerCompat;
57 import com.android.launcher3.compat.UserHandleCompat;
58 import com.android.launcher3.compat.UserManagerCompat;
59 import java.lang.ref.WeakReference;
60 import java.net.URISyntaxException;
61 import java.security.InvalidParameterException;
62 import java.text.Collator;
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.Collection;
66 import java.util.Collections;
67 import java.util.Comparator;
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.Iterator;
71 import java.util.List;
72 import java.util.Map.Entry;
73 import java.util.Set;
74
75
76 /**
77 * Maintains in-memory state of the Launcher. It is expected that there should be only one
78 * LauncherModel object held in a static. Also provide APIs for updating the database state
79 * for the Launcher.
80 */
81 public class LauncherModel extends BroadcastReceiver implements LauncherAppsCompat.OnAppsChangedCallbackC🔵
82 static final boolean DEBUG_LOADERS = false;
83
84 private static final boolean DEBUG_RECEIVER = false;
85
86 private static final boolean REMOVE_UNRESTORED_ICONS = true;
87
88 private static final boolean ADD_MANAGED_PROFILE_SHORTCUTS = false;
89
90 static final String TAG = "Launcher.Model";
91
92 public static final int LOADER_FLAG_NONE = 0;
93
94 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
95
96 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
97
98 // batch size for the workspace icons
99 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
100
101 private static final long INVALID_SCREEN_ID = -1L;
102
103 private final boolean mAppsCanBeOnRemoveableStorage;
104
105 private final boolean mOldContentProviderExists;
106
107 private final LauncherAppState mApp;
108
109 private final Object mLock = new Object();
110
111 private DeferredHandler mHandler = new DeferredHandler();
112
113 private LoaderTask mLoaderTask;
114
115 private boolean mIsLoaderTaskRunning;
116
117 /**
118 * Maintain a set of packages per user, for which we added a shortcut on the workspace.
119 */
120 private static final String INSTALLED_SHORTCUTS_SET_PREFIX = "installed_shortcuts_set_for_user_";
121
122 // Specific runnable types that are run on the main thread deferred handler, this allows us to
123 // clear all queued binding runnables when the Launcher activity is destroyed.
124 private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
125
126 private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
127
128 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
129
130 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
131
132 static {
133 sWorkerThread.start();
134 }
135
136 private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
137
138 // We start off with everything not loaded. After that, we assume that
139 // our monitoring of the package manager provides all updates and we never
140 // need to do a requery. These are only ever touched from the loader thread.
141 private boolean mWorkspaceLoaded;
142
143 private boolean mAllAppsLoaded;
144
145 // When we are loading pages synchronously, we can't just post the binding of items on the side
146 // pages as this delays the rotation process. Instead, we wait for a callback from the first
147 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
148 // a normal load, we also clear this set of Runnables.
149 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
150
151 private WeakReference<Callbacks> mCallbacks;
152
153 // < only access in worker thread >
154 // < only access in worker thread >
155 AllAppsList mBgAllAppsList;
156
157 // The lock that must be acquired before referencing any static bg data structures. Unlike
158 // other locks, this one can generally be held long-term because we never expect any of these
159 // static data structures to be referenced outside of the worker thread except on the first
160 // load after configuration change.
161 static final Object sBgLock = new Object();
162
163 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
164 // LauncherModel to their ids
165 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
166
167 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
168 // created by LauncherModel that are directly on the home screen (however, no widgets or
169 // shortcuts within folders).
170 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
171
172 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
173 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
174 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
175 new ArrayList<LauncherAppWidgetInfo>();
176
177 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
178 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
179
180 // sBgWorkspaceScreens is the ordered set of workspace screens.
181 // sBgWorkspaceScreens is the ordered set of workspace screens.
182 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
183
184 // sBgWidgetProviders is the set of widget providers including custom internal widgets
185 public static HashMap<ComponentName, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
186
187 public static boolean sWidgetProvidersDirty;
188
189 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
190 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages = new HashMap<UserHandleComp🔵
191
192 // </ only access in worker thread >
193 // </ only access in worker thread >
194
195 private IconCache mIconCache;
196
197 protected int mPreviousConfigMcc;
198
199 private final LauncherAppsCompat mLauncherApps;
200
201 private final UserManagerCompat mUserManager;
202
203 public interface Callbacks {
204 public abstract boolean setLoadOnResume();
205
206 public abstract int getCurrentWorkspaceScreen();
207
208 public abstract void startBinding();
209
210 public abstract void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, boolean forceAn🔵
211
212 public abstract void bindScreens(ArrayList<Long> orderedScreenIds);
213
214 public abstract void bindAddScreens(ArrayList<Long> orderedScreenIds);
215
216 public abstract void bindFolders(HashMap<Long, FolderInfo> folders);
217
218 public abstract void finishBindingItems();
219
220 public abstract void bindAppWidget(LauncherAppWidgetInfo info);
221
222 public abstract void bindAllApplications(ArrayList<AppInfo> apps);
223
224 public abstract void bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated🔵
225
226 public abstract void bindAppsUpdated(ArrayList<AppInfo> apps);
227
228 public abstract void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, ArrayList<ShortcutInfo🔵
229
230 public abstract void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
231
232 public abstract void updatePackageState(ArrayList<PackageInstallInfo> installInfo);
233
234 public abstract void updatePackageBadge(String packageName);
235
236 public abstract void bindComponentsRemoved(ArrayList<String> packageNames, ArrayList<AppInfo> app🔵
237
238 public abstract void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
239
240 public abstract void bindSearchablesChanged();
241
242 public abstract boolean isAllAppsButtonRank(int rank);
243
244 public abstract void onPageBoundSynchronously(int page);
245
246 public abstract void dumpLogsToLocalData();
247
248 public abstract void bindAddPendingItem(PendingAddItemInfo info, long container, long screenId, i🔵
249 }
250
251 public interface ItemInfoFilter {
252 public abstract boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
253 }
254
255 public interface ScreenPosProvider {
256 public abstract int getScreenIndex(ArrayList<Long> screenIDs);
257 }
258
259 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
260 Context context = app.getContext();
261 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
262 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
263 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
264 // resource string.
265 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
266 ProviderInfo providerInfo = context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY,🔵
267 ProviderInfo redirectProvider = context.getPackageManager().resolveContentProvider(redirectAuthor🔵
268 Log.d(TAG, "Old launcher provider: " + oldProvider);
269 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
270 if (mOldContentProviderExists) {
271 Log.d(TAG, "Old launcher provider exists.");
272 } else {
273 Log.d(TAG, "Old launcher provider does not exist.");
274 }
275 mApp = app;
276 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
277 mIconCache = iconCache;
278 final Resources res = context.getResources();
279 Configuration config = res.getConfiguration();
280 mPreviousConfigMcc = config.mcc;
281 mLauncherApps = LauncherAppsCompat.getInstance(context);
282 mUserManager = UserManagerCompat.getInstance(context);
283 }
284
285 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
286 * posted on the main thread handler. */
287 private void runOnMainThread(Runnable r) {
288 runOnMainThread(r, 0);
289 }
290
291 private void runOnMainThread(Runnable r, int type) {
292 if (sWorkerThread.getThreadId() == Process.myTid()) {
293 // If we are on the worker thread, post onto the main handler
294 mHandler.post(r);
295 } else {
296 r.run();
297 }
298 }
299
300 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
301 * posted on the worker thread handler. */
302 private static void runOnWorkerThread(Runnable r) {
303 if (sWorkerThread.getThreadId() == Process.myTid()) {
304 r.run();
305 } else {
306 // If we are not on the worker thread, then post to the worker handler
307 sWorker.post(r);
308 }
309 }
310
311 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
312 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
313 }
314
315 public void setPackageState(final ArrayList<PackageInstallInfo> installInfo) {
316 // Process the updated package state
317 Runnable r = new Runnable() {
318 public void run() {
319 Callbacks callbacks = getCallback();
320 if (callbacks != null) {
321 callbacks.updatePackageState(installInfo);
322 }
323 }
324 };
325 mHandler.post(r);
326 }
327
328 public void updatePackageBadge(final String packageName) {
329 // Process the updated package badge
330 Runnable r = new Runnable() {
331 public void run() {
332 Callbacks callbacks = getCallback();
333 if (callbacks != null) {
334 callbacks.updatePackageBadge(packageName);
335 }
336 }
337 };
338 mHandler.post(r);
339 }
340
341 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
342 final Callbacks callbacks = getCallback();
343
344 if (allAppsApps == null) {
345 throw new RuntimeException("allAppsApps must not be null");
346 }
347 if (allAppsApps.isEmpty()) {
348 return;
349 }
350
351 // Process the newly added applications and add them to the database first
352 Runnable r = new Runnable() {
353 public void run() {
354 runOnMainThread(new Runnable() {
355 public void run() {
356 Callbacks cb = getCallback();
357 if (callbacks == cb && cb != null) {
358 callbacks.bindAppsAdded(null, null, null, allAppsApps);
359 }
360 }
361 });
362 }
363 };
364 runOnWorkerThread(r);
365 }
366
367 public void addAndBindAddedWorkspaceApps(final Context context,
368 final ArrayList<ItemInfo> workspaceApps) {
369 addAndBindAddedWorkspaceApps(context, workspaceApps,
370 new ScreenPosProvider() {
371
372 @Override
373 public int getScreenIndex(ArrayList<Long> screenIDs) {
374 return screenIDs.isEmpty() ? 0 : 1;
375 }
376 }, 1, false);
377 }
378
379 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<Rect> occupiedPos,
380 int[] xy, int spanX, int spanY) {
381 LauncherAppState app = LauncherAppState.getInstance();
382 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
383 final int xCount = (int) grid.numColumns;
384 final int yCount = (int) grid.numRows;
385 boolean[][] occupied = new boolean[xCount][yCount];
386 if (occupiedPos != null) {
387 for (Rect r : occupiedPos) {
388 for (int x = r.left; 0 <= x && x < r.right && x < xCount; x++) {
389 for (int y = r.top; 0 <= y && y < r.bottom && y < yCount; y++) {
390 occupied[x][y] = true;
391 }
392 }
393 }
394 }
395 return CellLayout.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
396 }
397
398 /**
399 * Find a position on the screen for the given size or adds a new screen.
400 * @return screenId and the coordinates for the item.
401 */
402 private static Pair<Long, int[]> findSpaceForItem(
403 Context context,
404 ScreenPosProvider preferredScreen,
405 int fallbackStartScreen,
406 ArrayList<Long> workspaceScreens,
407 ArrayList<Long> addedWorkspaceScreensFinal,
408 int spanX, int spanY) {
409 // Load position of items which are on the desktop. We can't use sBgItemsIdMap because
410 // loadWorkspace() may not have been called.
411 final ContentResolver cr = context.getContentResolver();
412 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
413 new String[] {
414 LauncherSettings.Favorites.SCREEN,
415 LauncherSettings.Favorites.CELLX,
416 LauncherSettings.Favorites.CELLY,
417 LauncherSettings.Favorites.SPANX,
418 LauncherSettings.Favorites.SPANY,
419 LauncherSettings.Favorites.CONTAINER
420 },
421 "container=?",
422 new String[] { Integer.toString(LauncherSettings.Favorites.CONTAINER_DESKTOP) },
423 null);
424
425 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
426 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
427 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
428 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
429 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
430 LongSparseArray<ArrayList<Rect>> screenItems = new LongSparseArray<ArrayList<Rect>>();
431 try {
432 while (c.moveToNext()) {
433 Rect rect = new Rect();
434 rect.left = c.getInt(cellXIndex);
435 rect.top = c.getInt(cellYIndex);
436 rect.right = rect.left + Math.max(1, c.getInt(spanXIndex));
437 rect.bottom = rect.top + Math.max(1, c.getInt(spanYIndex));
438
439 long screenId = c.getInt(screenIndex);
440 ArrayList<Rect> items = screenItems.get(screenId);
441 if (items == null) {
442 items = new ArrayList<Rect>();
443 screenItems.put(screenId, items);
444 }
445 items.add(rect);
446 }
447 } catch (Exception e) {
448 screenItems.clear();
449 } finally {
450 c.close();
451 }
452
453 // Find appropriate space for the item.
454 long screenId = 0;
455 int[] cordinates = new int[2];
456 boolean found = false;
457
458 int screenCount = workspaceScreens.size();
459 // First check the preferred screen.
460 int preferredScreenIndex = preferredScreen.getScreenIndex(workspaceScreens);
461 if (preferredScreenIndex < screenCount) {
462 screenId = workspaceScreens.get(preferredScreenIndex);
463 found = findNextAvailableIconSpaceInScreen(
464 screenItems.get(screenId), cordinates, spanX, spanY);
465 }
466
467 if (!found) {
468 // Search on any of the screens.
469 for (int screen = fallbackStartScreen; screen < screenCount; screen++) {
470 screenId = workspaceScreens.get(screen);
471 if (findNextAvailableIconSpaceInScreen(
472 screenItems.get(screenId), cordinates, spanX, spanY)) {
473 // We found a space for it
474 found = true;
475 break;
476 }
477 }
478 }
479
480 if (!found) {
481 // Still no position found. Add a new screen to the end.
482 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
483
484 // Save the screen id for binding in the workspace
485 workspaceScreens.add(screenId);
486 addedWorkspaceScreensFinal.add(screenId);
487
488 // If we still can't find an empty space, then God help us all!!!
489 if (!findNextAvailableIconSpaceInScreen(
490 screenItems.get(screenId), cordinates, spanX, spanY)) {
491 throw new RuntimeException("Can't find space to add the item");
492 }
493 }
494 return Pair.create(screenId, cordinates);
495 }
496
497 /**
498 * Adds the provided items to the workspace.
499 * @param preferredScreen the screen where we should try to add the app first
500 * @param fallbackStartScreen the screen to start search for empty space if
501 * preferredScreen is not available.
502 */
503 public void addAndBindPendingItem(
504 final Context context,
505 final PendingAddItemInfo addInfo,
506 final ScreenPosProvider preferredScreen,
507 final int fallbackStartScreen) {
508 final Callbacks callbacks = getCallback();
509 // Process the newly added applications and add them to the database first
510 Runnable r = new Runnable() {
511 public void run() {
512 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
513 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
514
515 // Find appropriate space for the item.
516 Pair<Long, int[]> coords = findSpaceForItem(context, preferredScreen,
517 fallbackStartScreen, workspaceScreens, addedWorkspaceScreensFinal,
518 addInfo.spanX,
519 addInfo.spanY);
520 final long screenId = coords.first;
521 final int[] cordinates = coords.second;
522
523 // Update the workspace screens
524 updateWorkspaceScreenOrder(context, workspaceScreens);
525 runOnMainThread(new Runnable() {
526 public void run() {
527 Callbacks cb = getCallback();
528 if (callbacks == cb && cb != null) {
529 cb.bindAddScreens(addedWorkspaceScreensFinal);
530 cb.bindAddPendingItem(addInfo,
531 LauncherSettings.Favorites.CONTAINER_DESKTOP,
532 screenId, cordinates, addInfo.spanX, addInfo.spanY);
533 }
534 }
535 });
536 }
537 };
538 runOnWorkerThread(r);
539 }
540
541 /**
542 * Adds the provided items to the workspace.
543 * @param preferredScreen the screen where we should try to add the app first
544 * @param fallbackStartScreen the screen to start search for empty space if
545 * preferredScreen is not available.
546 */
547 public void addAndBindAddedWorkspaceApps(final Context context,
548 final ArrayList<ItemInfo> workspaceApps,
549 final ScreenPosProvider preferredScreen,
550 final int fallbackStartScreen,
551 final boolean allowDuplicate) {
552 final Callbacks callbacks = getCallback();
553 if (workspaceApps.isEmpty()) {
554 return;
555 }
556 // Process the newly added applications and add them to the database first
557 Runnable r = new Runnable() {
558 public void run() {
559 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
560 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
561
562 // Get the list of workspace screens. We need to append to this list and
563 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
564 // called.
565 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
566 synchronized(sBgLock) {
567 for (ItemInfo item : workspaceApps) {
568 if (!allowDuplicate) {
569 // Short-circuit this logic if the icon exists somewhere on the workspace
570 if (shortcutExists(context, item.title.toString(),
571 item.getIntent(), item.user)) {
572 continue;
573 }
574 }
575
576 // Find appropriate space for the item.
577 Pair<Long, int[]> coords = findSpaceForItem(context, preferredScreen,
578 fallbackStartScreen, workspaceScreens, addedWorkspaceScreensFinal,
579 1, 1);
580 long screenId = coords.first;
581 int[] cordinates = coords.second;
582
583 ShortcutInfo shortcutInfo;
584 if (item instanceof ShortcutInfo) {
585 shortcutInfo = (ShortcutInfo) item;
586 } else if (item instanceof AppInfo) {
587 shortcutInfo = ((AppInfo) item).makeShortcut();
588 } else {
589 throw new RuntimeException("Unexpected info type");
590 }
591
592 // Add the shortcut to the db
593 addItemToDatabase(context, shortcutInfo,
594 LauncherSettings.Favorites.CONTAINER_DESKTOP,
595 screenId, cordinates[0], cordinates[1], false);
596 // Save the ShortcutInfo for binding in the workspace
597 addedShortcutsFinal.add(shortcutInfo);
598 }
599 }
600
601 // Update the workspace screens
602 updateWorkspaceScreenOrder(context, workspaceScreens);
603
604 if (!addedShortcutsFinal.isEmpty()) {
605 runOnMainThread(new Runnable() {
606 public void run() {
607 Callbacks cb = getCallback();
608 if (callbacks == cb && cb != null) {
609 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
610 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
611 if (!addedShortcutsFinal.isEmpty()) {
612 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
613 long lastScreenId = info.screenId;
614 for (ItemInfo i : addedShortcutsFinal) {
615 if (i.screenId == lastScreenId) {
616 addAnimated.add(i);
617 } else {
618 addNotAnimated.add(i);
619 }
620 }
621 }
622 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
623 addNotAnimated, addAnimated, null);
624 }
625 }
626 });
627 }
628 }
629 };
630 runOnWorkerThread(r);
631 }
632
633 public void unbindItemInfosAndClearQueuedBindRunnables() {
634 if (sWorkerThread.getThreadId() == Process.myTid()) {
635 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
636 "main thread");
637 }
638
639 // Clear any deferred bind runnables
640 synchronized (mDeferredBindRunnables) {
641 mDeferredBindRunnables.clear();
642 }
643 // Remove any queued bind runnables
644 mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
645 // Unbind all the workspace items
646 unbindWorkspaceItemsOnMainThread();
647 }
648
649 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
650 void unbindWorkspaceItemsOnMainThread() {
651 // Ensure that we don't use the same workspace items data structure on the main thread
652 // by making a copy of workspace items first.
653 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
654 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
655 synchronized (sBgLock) {
656 tmpWorkspaceItems.addAll(sBgWorkspaceItems);
657 tmpAppWidgets.addAll(sBgAppWidgets);
658 }
659 Runnable r = new Runnable() {
660 @Override
661 public void run() {
662 for (ItemInfo item : tmpWorkspaceItems) {
663 item.unbind();
664 }
665 for (ItemInfo item : tmpAppWidgets) {
666 item.unbind();
667 }
668 }
669 };
670 runOnMainThread(r);
671 }
672
673 /**
674 * Adds an item to the DB if it was not created previously, or move it to a new
675 * <container, screen, cellX, cellY>
676 */
677 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
678 long screenId, int cellX, int cellY) {
679 if (item.container == ItemInfo.NO_ID) {
680 // From all apps
681 addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
682 } else {
683 // From somewhere else
684 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
685 }
686 }
687
688 static void checkItemInfoLocked(
689 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
690 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
691 if (modelItem != null && item != modelItem) {
692 // check all the data is consistent
693 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
694 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
695 ShortcutInfo shortcut = (ShortcutInfo) item;
696 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
697 modelShortcut.intent.filterEquals(shortcut.intent) &&
698 modelShortcut.id == shortcut.id &&
699 modelShortcut.itemType == shortcut.itemType &&
700 modelShortcut.container == shortcut.container &&
701 modelShortcut.screenId == shortcut.screenId &&
702 modelShortcut.cellX == shortcut.cellX &&
703 modelShortcut.cellY == shortcut.cellY &&
704 modelShortcut.spanX == shortcut.spanX &&
705 modelShortcut.spanY == shortcut.spanY &&
706 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
707 (modelShortcut.dropPos != null &&
708 shortcut.dropPos != null &&
709 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
710 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
711 // For all intents and purposes, this is the same object
712 return;
713 }
714 }
715
716 // the modelItem needs to match up perfectly with item if our model is
717 // to be consistent with the database-- for now, just require
718 // modelItem == item or the equality check above
719 String msg = "item: " + ((item != null) ? item.toString() : "null") +
720 "modelItem: " +
721 ((modelItem != null) ? modelItem.toString() : "null") +
722 "Error: ItemInfo passed to checkItemInfo doesn't match original";
723 RuntimeException e = new RuntimeException(msg);
724 if (stackTrace != null) {
725 e.setStackTrace(stackTrace);
726 }
727 throw e;
728 }
729 }
730
731 static void checkItemInfo(final ItemInfo item) {
732 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
733 final long itemId = item.id;
734 Runnable r = new Runnable() {
735 public void run() {
736 synchronized (sBgLock) {
737 checkItemInfoLocked(itemId, item, stackTrace);
738 }
739 }
740 };
741 runOnWorkerThread(r);
742 }
743
744 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
745 final ItemInfo item, final String callingFunction) {
746 final long itemId = item.id;
747 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
748 final ContentResolver cr = context.getContentResolver();
749
750 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
751 Runnable r = new Runnable() {
752 public void run() {
753 cr.update(uri, values, null, null);
754 updateItemArrays(item, itemId, stackTrace);
755 }
756 };
757 runOnWorkerThread(r);
758 }
759
760 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
761 final ArrayList<ItemInfo> items, final String callingFunction) {
762 final ContentResolver cr = context.getContentResolver();
763
764 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
765 Runnable r = new Runnable() {
766 public void run() {
767 ArrayList<ContentProviderOperation> ops =
768 new ArrayList<ContentProviderOperation>();
769 int count = items.size();
770 for (int i = 0; i < count; i++) {
771 ItemInfo item = items.get(i);
772 final long itemId = item.id;
773 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
774 ContentValues values = valuesList.get(i);
775
776 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
777 updateItemArrays(item, itemId, stackTrace);
778
779 }
780 try {
781 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
782 } catch (Exception e) {
783 e.printStackTrace();
784 }
785 }
786 };
787 runOnWorkerThread(r);
788 }
789
790 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
791 // Lock on mBgLock *after* the db operation
792 synchronized (sBgLock) {
793 checkItemInfoLocked(itemId, item, stackTrace);
794
795 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
796 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
797 // Item is in a folder, make sure this folder exists
798 if (!sBgFolders.containsKey(item.container)) {
799 // An items container is being set to a that of an item which is not in
800 // the list of Folders.
801 String msg = "item: " + item + " container being set to: " +
802 item.container + ", not in the list of folders";
803 Log.e(TAG, msg);
804 }
805 }
806
807 // Items are added/removed from the corresponding FolderInfo elsewhere, such
808 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
809 // that are on the desktop, as appropriate
810 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
811 if (modelItem != null &&
812 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
813 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
814 switch (modelItem.itemType) {
815 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
816 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
817 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
818 if (!sBgWorkspaceItems.contains(modelItem)) {
819 sBgWorkspaceItems.add(modelItem);
820 }
821 break;
822 default:
823 break;
824 }
825 } else {
826 sBgWorkspaceItems.remove(modelItem);
827 }
828 }
829 }
830
831 /**
832 * Move an item in the DB to a new <container, screen, cellX, cellY>
833 */
834 static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
835 final long screenId, final int cellX, final int cellY) {
836 item.container = container;
837 item.cellX = cellX;
838 item.cellY = cellY;
839
840 // We store hotseat items in canonical form which is this orientation invariant position
841 // in the hotseat
842 if (context instanceof Launcher && screenId < 0 &&
843 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
844 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
845 } else {
846 item.screenId = screenId;
847 }
848
849 final ContentValues values = new ContentValues();
850 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
851 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
852 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
853 values.put(LauncherSettings.Favorites.RANK, item.rank);
854 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
855
856 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
857 }
858
859 /**
860 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
861 * cellX, cellY have already been updated on the ItemInfos.
862 */
863 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
864 final long container, final int screen) {
865
866 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
867 int count = items.size();
868
869 for (int i = 0; i < count; i++) {
870 ItemInfo item = items.get(i);
871 item.container = container;
872
873 // We store hotseat items in canonical form which is this orientation invariant position
874 // in the hotseat
875 if (context instanceof Launcher && screen < 0 &&
876 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
877 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
878 item.cellY);
879 } else {
880 item.screenId = screen;
881 }
882
883 final ContentValues values = new ContentValues();
884 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
885 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
886 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
887 values.put(LauncherSettings.Favorites.RANK, item.rank);
888 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
889
890 contentValues.add(values);
891 }
892 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
893 }
894
895 /**
896 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
897 */
898 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
899 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
900 item.container = container;
901 item.cellX = cellX;
902 item.cellY = cellY;
903 item.spanX = spanX;
904 item.spanY = spanY;
905
906 // We store hotseat items in canonical form which is this orientation invariant position
907 // in the hotseat
908 if (context instanceof Launcher && screenId < 0 &&
909 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
910 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
911 } else {
912 item.screenId = screenId;
913 }
914
915 final ContentValues values = new ContentValues();
916 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
917 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
918 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
919 values.put(LauncherSettings.Favorites.RANK, item.rank);
920 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
921 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
922 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
923
924 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
925 }
926
927 /**
928 * Update an item to the database in a specified container.
929 */
930 static void updateItemInDatabase(Context context, final ItemInfo item) {
931 final ContentValues values = new ContentValues();
932 item.onAddToDatabase(context, values);
933 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
934 }
935
936 /**
937 * Returns true if the shortcuts already exists in the database.
938 * we identify a shortcut by its title and intent.
939 */
940 static boolean shortcutExists(Context context, String title, Intent intent,
941 UserHandleCompat user) {
942 final ContentResolver cr = context.getContentResolver();
943 final Intent intentWithPkg, intentWithoutPkg;
944
945 if (intent.getComponent() != null) {
946 // If component is not null, an intent with null package will produce
947 // the same result and should also be a match.
948 if (intent.getPackage() != null) {
949 intentWithPkg = intent;
950 intentWithoutPkg = new Intent(intent).setPackage(null);
951 } else {
952 intentWithPkg = new Intent(intent).setPackage(
953 intent.getComponent().getPackageName());
954 intentWithoutPkg = intent;
955 }
956 } else {
957 intentWithPkg = intent;
958 intentWithoutPkg = intent;
959 }
960 String userSerial = Long.toString(UserManagerCompat.getInstance(context)
961 .getSerialNumberForUser(user));
962 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
963 new String[] { "title", "intent", "profileId" },
964 "title=? and (intent=? or intent=?) and profileId=?",
965 new String[] { title, intentWithPkg.toUri(0), intentWithoutPkg.toUri(0), userSerial },
966 null);
967 try {
968 return c.moveToFirst();
969 } finally {
970 c.close();
971 }
972 }
973
974 /**
975 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
976 */
977 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
978 final ContentResolver cr = context.getContentResolver();
979 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
980 "_id=? and (itemType=? or itemType=?)",
981 new String[] { String.valueOf(id),
982 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
983
984 try {
985 if (c.moveToFirst()) {
986 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
987 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
988 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
989 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
990 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
991 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
992
993 FolderInfo folderInfo = null;
994 switch (c.getInt(itemTypeIndex)) {
995 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
996 folderInfo = findOrMakeFolder(folderList, id);
997 break;
998 }
999
1000 folderInfo.title = c.getString(titleIndex);
1001 folderInfo.id = id;
1002 folderInfo.container = c.getInt(containerIndex);
1003 folderInfo.screenId = c.getInt(screenIndex);
1004 folderInfo.cellX = c.getInt(cellXIndex);
1005 folderInfo.cellY = c.getInt(cellYIndex);
1006
1007 return folderInfo;
1008 }
1009 } finally {
1010 c.close();
1011 }
1012
1013 return null;
1014 }
1015
1016 /**
1017 * Add an item to the database in a specified container. Sets the container, screen, cellX and
1018 * cellY fields of the item. Also assigns an ID to the item.
1019 */
1020 static void addItemToDatabase(Context context, final ItemInfo item, final long container,
1021 final long screenId, final int cellX, final int cellY, final boolean notify) {
1022 item.container = container;
1023 item.cellX = cellX;
1024 item.cellY = cellY;
1025 // We store hotseat items in canonical form which is this orientation invariant position
1026 // in the hotseat
1027 if (context instanceof Launcher && screenId < 0 &&
1028 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1029 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1030 } else {
1031 item.screenId = screenId;
1032 }
1033
1034 final ContentValues values = new ContentValues();
1035 final ContentResolver cr = context.getContentResolver();
1036 item.onAddToDatabase(context, values);
1037
1038 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1039 values.put(LauncherSettings.Favorites._ID, item.id);
1040
1041 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1042 Runnable r = new Runnable() {
1043 public void run() {
1044 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
1045 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
1046
1047 // Lock on mBgLock *after* the db operation
1048 synchronized (sBgLock) {
1049 checkItemInfoLocked(item.id, item, stackTrace);
1050 sBgItemsIdMap.put(item.id, item);
1051 switch (item.itemType) {
1052 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1053 sBgFolders.put(item.id, (FolderInfo) item);
1054 // Fall through
1055 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1056 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1057 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1058 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1059 sBgWorkspaceItems.add(item);
1060 } else {
1061 if (!sBgFolders.containsKey(item.container)) {
1062 // Adding an item to a folder that doesn't exist.
1063 String msg = "adding item: " + item + " to a folder that " +
1064 " doesn't exist";
1065 Log.e(TAG, msg);
1066 }
1067 }
1068 break;
1069 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1070 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1071 break;
1072 }
1073 }
1074 }
1075 };
1076 runOnWorkerThread(r);
1077 }
1078
1079 /**
1080 * Creates a new unique child id, for a given cell span across all layouts.
1081 */
1082 static int getCellLayoutChildId(
1083 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1084 return (((int) container & 0xFF) << 24)
1085 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1086 }
1087
1088 private static ArrayList<ItemInfo> getItemsByPackageName(
1089 final String pn, final UserHandleCompat user) {
1090 ItemInfoFilter filter = new ItemInfoFilter() {
1091 @Override
1092 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1093 return cn.getPackageName().equals(pn) && info.user.equals(user);
1094 }
1095 };
1096 return filterItemInfos(sBgItemsIdMap.values(), filter);
1097 }
1098
1099 /**
1100 * Removes all the items from the database corresponding to the specified package.
1101 */
1102 static void deletePackageFromDatabase(Context context, final String pn,
1103 final UserHandleCompat user) {
1104 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1105 }
1106
1107 /**
1108 * Removes the specified item from the database
1109 * @param context
1110 * @param item
1111 */
1112 static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1113 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1114 items.add(item);
1115 deleteItemsFromDatabase(context, items);
1116 }
1117
1118 /**
1119 * Removes the specified items from the database
1120 * @param context
1121 * @param item
1122 */
1123 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1124 final ContentResolver cr = context.getContentResolver();
1125 Runnable r = new Runnable() {
1126 public void run() {
1127 for (ItemInfo item : items) {
1128 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id, false);
1129 cr.delete(uri, null, null);
1130 // Lock on mBgLock *after* the db operation
1131 synchronized(sBgLock) {
1132 switch (item.itemType) {
1133 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
1134 sBgFolders.remove(item.id);
1135 for (ItemInfo info : sBgItemsIdMap.values()) {
1136 if (info.container == item.id) {
1137 // We are deleting a folder which still contains items that
1138 // think they are contained by that folder.
1139 String msg = (((("deleting a folder (" + item) + ") which still "🔵
1140 Log.e(TAG, msg);
1141 }
1142 }
1143 sBgWorkspaceItems.remove(item);
1144 break;
1145 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
1146 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
1147 sBgWorkspaceItems.remove(item);
1148 break;
1149 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
1150 sBgAppWidgets.remove(((LauncherAppWidgetInfo) (item)));
1151 break;
1152 }
1153 sBgItemsIdMap.remove(item.id);
1154 }
1155 }
1156 }
1157 };
1158 runOnWorkerThread(r);
1159 }
1160
1161 /**
1162 * Update the order of the workspace screens in the database. The array list contains
1163 * a list of screen ids in the order that they should appear.
1164 */
1165 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1166 // Log to disk
1167 Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
1168 Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
1169
1170 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1171 final ContentResolver cr = context.getContentResolver();
1172 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1173
1174 // Remove any negative screen ids -- these aren't persisted
1175 Iterator<Long> iter = screensCopy.iterator();
1176 while (iter.hasNext()) {
1177 long id = iter.next();
1178 if (id < 0) {
1179 iter.remove();
1180 }
1181 }
1182
1183 Runnable r = new Runnable() {
1184 @Override
1185 public void run() {
1186 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1187 // Clear the table
1188 ops.add(ContentProviderOperation.newDelete(uri).build());
1189 int count = screensCopy.size();
1190 for (int i = 0; i < count; i++) {
1191 ContentValues v = new ContentValues();
1192 long screenId = screensCopy.get(i);
1193 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1194 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1195 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1196 }
1197
1198 try {
1199 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1200 } catch (Exception ex) {
1201 throw new RuntimeException(ex);
1202 }
1203
1204 synchronized (sBgLock) {
1205 sBgWorkspaceScreens.clear();
1206 sBgWorkspaceScreens.addAll(screensCopy);
1207 }
1208 }
1209 };
1210 runOnWorkerThread(r);
1211 }
1212
1213 /**
1214 * Remove the contents of the specified folder from the database
1215 */
1216 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1217 final ContentResolver cr = context.getContentResolver();
1218 Runnable r = new Runnable() {
1219 public void run() {
1220 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1221 // Lock on mBgLock *after* the db operation
1222 synchronized(sBgLock) {
1223 sBgItemsIdMap.remove(info.id);
1224 sBgFolders.remove(info.id);
1225 sBgWorkspaceItems.remove(info);
1226 }
1227 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, (LauncherSettings.Favor🔵
1228 // Lock on mBgLock *after* the db operation
1229 synchronized(sBgLock) {
1230 for (ItemInfo childInfo : info.contents) {
1231 sBgItemsIdMap.remove(childInfo.id);
1232 }
1233 }
1234 }
1235 };
1236 runOnWorkerThread(r);
1237 }
1238
1239 /**
1240 * Set this as the current Launcher activity object for the loader.
1241 */
1242 public void initialize(Callbacks callbacks) {
1243 synchronized (mLock) {
1244 mCallbacks = new WeakReference<Callbacks>(callbacks);
1245 }
1246 }
1247
1248 @Override
1249 public void onPackageChanged(String packageName, UserHandleCompat user) {
1250 int op = PackageUpdatedTask.OP_UPDATE;
1251 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1252 user));
1253 }
1254
1255 @Override
1256 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1257 int op = PackageUpdatedTask.OP_REMOVE;
1258 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1259 user));
1260 }
1261
1262 @Override
1263 public void onPackageAdded(String packageName, UserHandleCompat user) {
1264 int op = PackageUpdatedTask.OP_ADD;
1265 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1266 user));
1267 }
1268
1269 @Override
1270 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1271 boolean replacing) {
1272 if (!replacing) {
1273 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1274 user));
1275 if (mAppsCanBeOnRemoveableStorage) {
1276 // Only rebind if we support removable storage. It catches the
1277 // case where
1278 // apps on the external sd card need to be reloaded
1279 startLoaderFromBackground();
1280 }
1281 } else {
1282 // If we are replacing then just update the packages in the list
1283 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1284 packageNames, user));
1285 }
1286 }
1287
1288 @Override
1289 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1290 boolean replacing) {
1291 if (!replacing) {
1292 enqueuePackageUpdated(new PackageUpdatedTask(
1293 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1294 user));
1295 }
1296 }
1297
1298 /**
1299 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1300 * ACTION_PACKAGE_CHANGED.
1301 */
1302 @Override
1303 public void onReceive(Context context, Intent intent) {
1304 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1305
1306 final String action = intent.getAction();
1307 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1308 // If we have changed locale we need to clear out the labels in all apps/workspace.
1309 forceReload();
1310 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1311 // Check if configuration change was an mcc/mnc change which would affect app resources
1312 // and we would need to clear out the labels in all apps/workspace. Same handling as
1313 // above for ACTION_LOCALE_CHANGED
1314 Configuration currentConfig = context.getResources().getConfiguration();
1315 if (mPreviousConfigMcc != currentConfig.mcc) {
1316 Log.d(TAG, "Reload apps on config change. curr_mcc:"
1317 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
1318 forceReload();
1319 }
1320 // Update previousConfig
1321 mPreviousConfigMcc = currentConfig.mcc;
1322 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1323 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1324 Callbacks callbacks = getCallback();
1325 if (callbacks != null) {
1326 callbacks.bindSearchablesChanged();
1327 }
1328 }
1329 }
1330
1331 void forceReload() {
1332 resetLoadedState(true, true);
1333
1334 // Do this here because if the launcher activity is running it will be restarted.
1335 // If it's not running startLoaderFromBackground will merely tell it that it needs
1336 // to reload.
1337 startLoaderFromBackground();
1338 }
1339
1340 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1341 synchronized (mLock) {
1342 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1343 // mWorkspaceLoaded to true later
1344 stopLoaderLocked();
1345 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1346 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1347 }
1348 }
1349
1350 /**
1351 * When the launcher is in the background, it's possible for it to miss paired
1352 * configuration changes. So whenever we trigger the loader from the background
1353 * tell the launcher that it needs to re-run the loader when it comes back instead
1354 * of doing it now.
1355 */
1356 public void startLoaderFromBackground() {
1357 boolean runLoader = false;
1358 Callbacks callbacks = getCallback();
1359 if (callbacks != null) {
1360 // Only actually run the loader if they're not paused.
1361 if (!callbacks.setLoadOnResume()) {
1362 runLoader = true;
1363 }
1364 }
1365 if (runLoader) {
1366 startLoader(false, PagedView.INVALID_RESTORE_PAGE);
1367 }
1368 }
1369
1370 // If there is already a loader task running, tell it to stop.
1371 // returns true if isLaunching() was true on the old task
1372 private boolean stopLoaderLocked() {
1373 boolean isLaunching = false;
1374 LoaderTask oldTask = mLoaderTask;
1375 if (oldTask != null) {
1376 if (oldTask.isLaunching()) {
1377 isLaunching = true;
1378 }
1379 oldTask.stopLocked();
1380 }
1381 return isLaunching;
1382 }
1383
1384 public boolean isCurrentCallbacks(Callbacks callbacks) {
1385 return (mCallbacks != null && mCallbacks.get() == callbacks);
1386 }
1387
1388 public void startLoader(boolean isLaunching, int synchronousBindPage) {
1389 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
1390 }
1391
1392 public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
1393 synchronized (mLock) {
1394 if (DEBUG_LOADERS) {
1395 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
1396 }
1397
1398 // Clear any deferred bind-runnables from the synchronized load process
1399 // We must do this before any loading/binding is scheduled below.
1400 synchronized (mDeferredBindRunnables) {
1401 mDeferredBindRunnables.clear();
1402 }
1403
1404 // Don't bother to start the thread if we know it's not going to do anything
1405 if (mCallbacks != null && mCallbacks.get() != null) {
1406 // If there is already one running, tell it to stop.
1407 // also, don't downgrade isLaunching if we're already running
1408 isLaunching = isLaunching || stopLoaderLocked();
1409 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
1410 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1411 && mAllAppsLoaded && mWorkspaceLoaded) {
1412 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1413 } else {
1414 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1415 sWorker.post(mLoaderTask);
1416 }
1417 }
1418 }
1419 }
1420
1421 void bindRemainingSynchronousPages() {
1422 // Post the remaining side pages to be loaded
1423 if (!mDeferredBindRunnables.isEmpty()) {
1424 Runnable[] deferredBindRunnables = null;
1425 synchronized (mDeferredBindRunnables) {
1426 deferredBindRunnables = mDeferredBindRunnables.toArray(
1427 new Runnable[mDeferredBindRunnables.size()]);
1428 mDeferredBindRunnables.clear();
1429 }
1430 for (final Runnable r : deferredBindRunnables) {
1431 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
1432 }
1433 }
1434 }
1435
1436 public void stopLoader() {
1437 synchronized (mLock) {
1438 if (mLoaderTask != null) {
1439 mLoaderTask.stopLocked();
1440 }
1441 }
1442 }
1443
1444 /**
1445 * Loads the workspace screen ids in an ordered list.
1446 */
1447 private static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1448 final ContentResolver contentResolver = context.getContentResolver();
1449 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1450
1451 // Get screens ordered by rank.
1452 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1453 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1454 ArrayList<Long> screenIds = new ArrayList<Long>();
1455 try {
1456 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1457 while (sc.moveToNext()) {
1458 try {
1459 screenIds.add(sc.getLong(idIndex));
1460 } catch (Exception e) {
1461 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1462 + " - invalid screens: " + e, true);
1463 }
1464 }
1465 } finally {
1466 sc.close();
1467 }
1468 return screenIds;
1469 }
1470
1471 public boolean isAllAppsLoaded() {
1472 return mAllAppsLoaded;
1473 }
1474
1475 boolean isLoadingWorkspace() {
1476 synchronized (mLock) {
1477 if (mLoaderTask != null) {
1478 return mLoaderTask.isLoadingWorkspace();
1479 }
1480 }
1481 return false;
1482 }
1483
1484 /**
1485 * Runnable for the thread that loads the contents of the launcher:
1486 * - workspace icons
1487 * - widgets
1488 * - all apps icons
1489 */
1490 private class LoaderTask implements Runnable {
1491 private Context mContext;
1492
1493 private boolean mIsLaunching;
1494
1495 private boolean mIsLoadingAndBindingWorkspace;
1496
1497 private boolean mStopped;
1498
1499 private boolean mLoadAndBindStepFinished;
1500
1501 private int mFlags;
1502
1503 LoaderTask(Context context, boolean isLaunching, int flags) {
1504 mContext = context;
1505 mIsLaunching = isLaunching;
1506 mFlags = flags;
1507 }
1508
1509 boolean isLaunching() {
1510 return mIsLaunching;
1511 }
1512
1513 boolean isLoadingWorkspace() {
1514 return mIsLoadingAndBindingWorkspace;
1515 }
1516
1517 private void loadAndBindWorkspace() {
1518 mIsLoadingAndBindingWorkspace = true;
1519
1520 // Load the workspace
1521 if (DEBUG_LOADERS) {
1522 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1523 }
1524
1525 if (!mWorkspaceLoaded) {
1526 loadWorkspace();
1527 synchronized (LoaderTask.this) {
1528 if (mStopped) {
1529 return;
1530 }
1531 mWorkspaceLoaded = true;
1532 }
1533 }
1534
1535 // Bind the workspace
1536 bindWorkspace(-1);
1537 }
1538
1539 private void waitForIdle() {
1540 // Wait until the either we're stopped or the other threads are done.
1541 // This way we don't start loading all apps until the workspace has settled
1542 // down.
1543 synchronized (LoaderTask.this) {
1544 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1545
1546 mHandler.postIdle(new Runnable() {
1547 public void run() {
1548 synchronized (LoaderTask.this) {
1549 mLoadAndBindStepFinished = true;
1550 if (DEBUG_LOADERS) {
1551 Log.d(TAG, "done with previous binding step");
1552 }
1553 LoaderTask.this.notify();
1554 }
1555 }
1556 });
1557
1558 while (!mStopped && !mLoadAndBindStepFinished) {
1559 try {
1560 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1561 // wait no longer than 1sec at a time
1562 this.wait(1000);
1563 } catch (InterruptedException ex) {
1564 // Ignore
1565 }
1566 }
1567 if (DEBUG_LOADERS) {
1568 Log.d(TAG, "waited "
1569 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1570 + "ms for previous step to finish binding");
1571 }
1572 }
1573 }
1574
1575 void runBindSynchronousPage(int synchronousBindPage) {
1576 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1577 // Ensure that we have a valid page index to load synchronously
1578 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1579 "valid page index");
1580 }
1581 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1582 // Ensure that we don't try and bind a specified page when the pages have not been
1583 // loaded already (we should load everything asynchronously in that case)
1584 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1585 }
1586 synchronized (mLock) {
1587 if (mIsLoaderTaskRunning) {
1588 // Ensure that we are never running the background loading at this point since
1589 // we also touch the background collections
1590 throw new RuntimeException("Error! Background loading is already running");
1591 }
1592 }
1593
1594 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1595 // data structures, we can't allow any other thread to touch that data, but because
1596 // this call is synchronous, we can get away with not locking).
1597
1598 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1599 // operations from the previous activity. We need to ensure that all queued operations
1600 // are executed before any synchronous binding work is done.
1601 mHandler.flush();
1602
1603 // Divide the set of loaded items into those that we are binding synchronously, and
1604 // everything else that is to be bound normally (asynchronously).
1605 bindWorkspace(synchronousBindPage);
1606 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1607 // arise from that.
1608 onlyBindAllApps();
1609 }
1610
1611 public void run() {
1612 synchronized (mLock) {
1613 mIsLoaderTaskRunning = true;
1614 }
1615 // Optimize for end-user experience: if the Launcher is up and // running with the
1616 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1617 // workspace first (default).
1618 keep_running: {
1619 // Elevate priority when Home launches for the first time to avoid
1620 // starving at boot time. Staring at a blank home is not cool.
1621 synchronized (mLock) {
1622 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
1623 (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
1624 android.os.Process.setThreadPriority(mIsLaunching
1625 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
1626 }
1627 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1628 loadAndBindWorkspace();
1629
1630 if (mStopped) {
1631 break keep_running;
1632 }
1633
1634 // Whew! Hard work done. Slow us down, and wait until the UI thread has
1635 // settled down.
1636 synchronized (mLock) {
1637 if (mIsLaunching) {
1638 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
1639 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1640 }
1641 }
1642 waitForIdle();
1643
1644 // second step
1645 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1646 loadAndBindAllApps();
1647
1648 // Restore the default thread priority after we are done loading items
1649 synchronized (mLock) {
1650 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1651 }
1652 }
1653
1654
1655 <<<<<<< LEFT
1656 if (LauncherAppState.isDisableAllApps()) {
1657 // Ensure that all the applications that are in the system are
1658 // represented on the home screen.
1659 verifyApplications();
1660
1661 ||||||| BASE
1662 /*d94z9sk0k4hf9j3ijd - note the base isn't actually empty, spork simply doesn't generate a base - gd930kw🔵
1663 =======
1664
1665 // Update the saved icons if necessary
1666 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1667 synchronized (sBgLock) {
1668 for (Object key : sBgDbIconCache.keySet()) {
1669 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1670 }
1671 sBgDbIconCache.clear();
1672
1673 >>>>>>> RIGHT
1674 }
1675
1676 // Clear out this reference, otherwise we end up holding it until all of the
1677 // callback runnables are done.
1678 mContext = null;
1679
1680 synchronized (mLock) {
1681 // If we are still the last one to be scheduled, remove ourselves.
1682 if (mLoaderTask == this) {
1683 mLoaderTask = null;
1684 }
1685 mIsLoaderTaskRunning = false;
1686 }
1687 }
1688
1689 public void stopLocked() {
1690 synchronized (LoaderTask.this) {
1691 mStopped = true;
1692 this.notify();
1693 }
1694 }
1695
1696 /**
1697 * Gets the callbacks object. If we've been stopped, or if the launcher object
1698 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1699 * object that was around when the deferred message was scheduled, and if there's
1700 * a new Callbacks object around then also return null. This will save us from
1701 * calling onto it with data that will be ignored.
1702 */
1703 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1704 synchronized (mLock) {
1705 if (mStopped) {
1706 return null;
1707 }
1708
1709 if (mCallbacks == null) {
1710 return null;
1711 }
1712
1713 final Callbacks callbacks = mCallbacks.get();
1714 if (callbacks != oldCallbacks) {
1715 return null;
1716 }
1717 if (callbacks == null) {
1718 Log.w(TAG, "no mCallbacks");
1719 return null;
1720 }
1721
1722 return callbacks;
1723 }
1724 }
1725
1726 // check & update map of what's occupied; used to discard overlapping/invalid items
1727 private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item) {
1728 LauncherAppState app = LauncherAppState.getInstance();
1729 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1730 final int countX = (int) grid.numColumns;
1731 final int countY = (int) grid.numRows;
1732
1733 long containerIndex = item.screenId;
1734 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1735 // Return early if we detect that an item is under the hotseat button
1736 if (mCallbacks == null ||
1737 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1738 Log.e(TAG, "Error loading shortcut into hotseat " + item
1739 + " into position (" + item.screenId + ":" + item.cellX + ","
1740 + item.cellY + ") occupied by all apps");
1741 return false;
1742 }
1743
1744 final ItemInfo[][] hotseatItems =
1745 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1746
1747 if (item.screenId >= grid.numHotseatIcons) {
1748 Log.e(TAG, "Error loading shortcut " + item
1749 + " into hotseat position " + item.screenId
1750 + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1)
1751 + ")");
1752 return false;
1753 }
1754
1755 if (hotseatItems != null) {
1756 if (hotseatItems[(int) item.screenId][0] != null) {
1757 Log.e(TAG, "Error loading shortcut into hotseat " + item
1758 + " into position (" + item.screenId + ":" + item.cellX + ","
1759 + item.cellY + ") occupied by "
1760 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1761 [(int) item.screenId][0]);
1762 return false;
1763 } else {
1764 hotseatItems[(int) item.screenId][0] = item;
1765 return true;
1766 }
1767 } else {
1768 final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
1769 items[(int) item.screenId][0] = item;
1770 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1771 return true;
1772 }
1773 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1774 // Skip further checking if it is not the hotseat or workspace container
1775 return true;
1776 }
1777
1778 if (!occupied.containsKey(item.screenId)) {
1779 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1780 occupied.put(item.screenId, items);
1781 }
1782
1783 final ItemInfo[][] screens = occupied.get(item.screenId);
1784 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1785 item.cellX < 0 || item.cellY < 0 ||
1786 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1787 Log.e(TAG, "Error loading shortcut " + item
1788 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1789 + item.cellX + "," + item.cellY
1790 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1791 return false;
1792 }
1793
1794 // Check if any workspace icons overlap with each other
1795 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1796 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1797 if (screens[x][y] != null) {
1798 Log.e(TAG, "Error loading shortcut " + item
1799 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1800 + x + "," + y
1801 + ") occupied by "
1802 + screens[x][y]);
1803 return false;
1804 }
1805 }
1806 }
1807 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1808 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1809 screens[x][y] = item;
1810 }
1811 }
1812
1813 return true;
1814 }
1815
1816 /** Clears all the sBg data structures */
1817 private void clearSBgDataStructures() {
1818 synchronized(sBgLock) {
1819 sBgWorkspaceItems.clear();
1820 sBgAppWidgets.clear();
1821 sBgFolders.clear();
1822 sBgItemsIdMap.clear();
1823 sBgWorkspaceScreens.clear();
1824 }
1825 }
1826
1827 private void loadWorkspace() {
1828 // Log to disk
1829 Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
1830 final long t = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
1831 final Context context = mContext;
1832 final ContentResolver contentResolver = context.getContentResolver();
1833 final PackageManager manager = context.getPackageManager();
1834 final boolean isSafeMode = manager.isSafeMode();
1835 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1836 final boolean isSdCardReady = context.registerReceiver(null, new IntentFilter(StartupReceiver🔵
1837 LauncherAppState app = LauncherAppState.getInstance();
1838 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1839 int countX = ((int) (grid.numColumns));
1840 int countY = ((int) (grid.numRows));
1841 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1842 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1843 LauncherAppState.getLauncherProvider().deleteDatabase();
1844 }
1845 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1846 // append the user's Launcher2 shortcuts
1847 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1848 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1849 } else {
1850 // Make sure the default workspace is loaded
1851 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1852 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1853 }
1854 synchronized(sBgLock) {
1855 clearSBgDataStructures();
1856 final HashSet<String> installingPkgs = PackageInstallerCompat.getInstance(mContext).updat🔵
1857 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1858 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1859 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION;
1860 if (DEBUG_LOADERS) {
1861 Log.d(TAG, "loading model from " + contentUri);
1862 }
1863 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1864 // +1 for the hotseat (it can be larger than the workspace)
1865 // Load workspace in reverse order to ensure that latest items are loaded first (and
1866 // before any earlier duplicates)
1867 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
1868 try {
1869 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1870 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1871 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1872 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYP🔵
1873 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1874 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_🔵
1875 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON🔵
1876 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAIN🔵
1877 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYP🔵
1878 final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWI🔵
1879 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites🔵
1880 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1881 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1882 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1883 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
1884 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
1885 final int rankIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.RANK);
1886 final int restoredIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED🔵
1887 final int profileIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE🔵
1888 // final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1889 // final int displayModeIndex = c.getColumnIndexOrThrow(
1890 // LauncherSettings.Favorites.DISPLAY_MODE);
1891 ShortcutInfo info;
1892 String intentDescription;
1893 LauncherAppWidgetInfo appWidgetInfo;
1894 int container;
1895 long id;
1896 Intent intent;
1897 UserHandleCompat user;
1898 while ((!mStopped) && c.moveToNext()) {
1899 try {
1900 int itemType = c.getInt(itemTypeIndex);
1901 boolean restored = 0 != c.getInt(restoredIndex);
1902 boolean allowMissingTarget = false;
1903 switch (itemType) {
1904 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
1905 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
1906 id = c.getLong(idIndex);
1907 intentDescription = c.getString(intentIndex);
1908 long serialNumber = c.getInt(profileIdIndex);
1909 user = mUserManager.getUserForSerialNumber(serialNumber);
1910 int promiseType = c.getInt(restoredIndex);
1911 int disabledState = 0;
1912 boolean itemReplaced = false;
1913 if (user == null) {
1914 // User has been deleted remove the item.
1915 itemsToRemove.add(id);
1916 continue;
1917 }
1918 try {
1919 intent = Intent.parseUri(intentDescription, 0);
1920 ComponentName cn = intent.getComponent();
1921 if ((cn != null) && (cn.getPackageName() != null)) {
1922 boolean validPkg = launcherApps.isPackageEnabledForProfile(cn🔵
1923 boolean validComponent = validPkg && launcherApps.isActivityE🔵
1924 if (validComponent) {
1925 if (restored) {
1926 // no special handling necessary for this item
1927 restoredRows.add(id);
1928 restored = false;
1929 }
1930 } else if (validPkg) {
1931 intent = null;
1932 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 🔵
1933 // We allow auto install apps to have their intent
1934 // updated after an install.
1935 intent = manager.getLaunchIntentForPackage(cn.getPack🔵
1936 if (intent != null) {
1937 ContentValues values = new ContentValues();
1938 values.put(LauncherSettings.Favorites.INTENT, int🔵
1939 updateItem(id, values);
1940 }
1941 }
1942 if (intent == null) {
1943 // The app is installed but the component is no
1944 // longer available.
1945 Launcher.addDumpLog(TAG, "Invalid component removed: 🔵
1946 itemsToRemove.add(id);
1947 continue;
1948 } else {
1949 // no special handling necessary for this item
1950 restoredRows.add(id);
1951 restored = false;
1952 }
1953 } else if (restored) {
1954 // Package is not yet available but might be
1955 // installed later.
1956 Launcher.addDumpLog(TAG, "package not yet restored: " + c🔵
1957 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 🔵
1958 // Restore has started once.
1959 } else if (installingPkgs.contains(cn.getPackageName())) 🔵
1960 // App restore has started. Update the flag
1961 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1962 ContentValues values = new ContentValues();
1963 values.put(LauncherSettings.Favorites.RESTORED, promi🔵
1964 updateItem(id, values);
1965 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_🔵
1966 // This is a common app. Try to replace this.
1967 int appType = CommonAppTypeParser.decodeItemTypeFromF🔵
1968 CommonAppTypeParser parser = new CommonAppTypeParser(🔵
1969 if (parser.findDefaultApp()) {
1970 // Default app found. Replace it.
1971 intent = parser.parsedIntent;
1972 cn = intent.getComponent();
1973 ContentValues values = parser.parsedValues;
1974 values.put(LauncherSettings.Favorites.RESTORED, 0🔵
1975 updateItem(id, values);
1976 restored = false;
1977 itemReplaced = true;
1978 } else if (REMOVE_UNRESTORED_ICONS) {
1979 Launcher.addDumpLog(TAG, "Unrestored package remo🔵
1980 itemsToRemove.add(id);
1981 continue;
1982 }
1983 } else if (REMOVE_UNRESTORED_ICONS) {
1984 Launcher.addDumpLog(TAG, "Unrestored package removed:🔵
1985 itemsToRemove.add(id);
1986 continue;
1987 }
1988 } else if (launcherApps.isAppEnabled(manager, cn.getPackageNa🔵
1989 // Package is present but not available.
1990 allowMissingTarget = true;
1991 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1992 } else if (!isSdCardReady) {
1993 // SdCard is not ready yet. Package might get available,
1994 // once it is ready.
1995 Launcher.addDumpLog(TAG, ("Invalid package: " + cn) + " (🔵
1996 HashSet<String> pkgs = sPendingPackages.get(user);
1997 if (pkgs == null) {
1998 pkgs = new HashSet<String>();
1999 sPendingPackages.put(user, pkgs);
2000 }
2001 pkgs.add(cn.getPackageName());
2002 allowMissingTarget = true;
2003 // Add the icon on the workspace anyway.
2004 } else {
2005 // Do not wait for external media load anymore.
2006 // Log the invalid package, and remove it
2007 Launcher.addDumpLog(TAG, "Invalid package removed: " + cn🔵
2008 itemsToRemove.add(id);
2009 continue;
2010 }
2011 } else if (cn == null) {
2012 // For shortcuts with no component, keep them as they are
2013 restoredRows.add(id);
2014 restored = false;
2015 }
2016 } catch (URISyntaxException e) {
2017 Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, tru🔵
2018 continue;
2019 }
2020 if (itemReplaced) {
2021 if (user.equals(UserHandleCompat.myUserHandle())) {
2022 info = getAppShortcutInfo(manager, intent, user, context, nul🔵
2023 } else {
2024 // Don't replace items for other profiles.
2025 itemsToRemove.add(id);
2026 continue;
2027 }
2028 } else if (restored) {
2029 if (user.equals(UserHandleCompat.myUserHandle())) {
2030 Launcher.addDumpLog(TAG, "constructing info for partially res🔵
2031 info = getRestoredItemInfo(c, titleIndex, intent, promiseType🔵
2032 intent = getRestoredItemIntent(c, context, intent);
2033 } else {
2034 // Don't restore items for other profiles.
2035 itemsToRemove.add(id);
2036 continue;
2037 }
2038 } else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATI🔵
2039 info = getAppShortcutInfo(manager, intent, user, context, c, icon🔵
2040 } else {
2041 info = getShortcutInfo(c, context, iconTypeIndex, iconPackageInde🔵
2042 // App shortcuts that used to be automatically added to Launcher
2043 // didn't always have the correct intent flags set, so do that
2044 // here
2045 if ((((intent.getAction() != null) && (intent.getCategories() != 🔵
2046 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_A🔵
2047 }
2048 }
2049 if (info != null) {
2050 info.id = id;
2051 info.intent = intent;
2052 container = c.getInt(containerIndex);
2053 info.container = container;
2054 info.screenId = c.getInt(screenIndex);
2055 info.cellX = c.getInt(cellXIndex);
2056 info.cellY = c.getInt(cellYIndex);
2057 info.rank = c.getInt(rankIndex);
2058 info.spanX = 1;
2059 info.spanY = 1;
2060 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2061 info.isDisabled = disabledState;
2062 if (isSafeMode && (!Utilities.isSystemApp(context, intent))) {
2063 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2064 }
2065 // check & update map of what's occupied
2066 if (!checkItemPlacement(occupied, info)) {
2067 itemsToRemove.add(id);
2068 break;
2069 }
2070 switch (container) {
2071 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
2072 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2073 sBgWorkspaceItems.add(info);
2074 break;
2075 default :
2076 // Item is in a user folder
2077 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, cont🔵
2078 folderInfo.add(info);
2079 break;
2080 }
2081 sBgItemsIdMap.put(info.id, info);
2082 } else {
2083 throw new RuntimeException("Unexpected null ShortcutInfo");
2084 }
2085 break;
2086 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
2087 id = c.getLong(idIndex);
2088 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2089 folderInfo.title = c.getString(titleIndex);
2090 folderInfo.id = id;
2091 container = c.getInt(containerIndex);
2092 folderInfo.container = container;
2093 folderInfo.screenId = c.getInt(screenIndex);
2094 folderInfo.cellX = c.getInt(cellXIndex);
2095 folderInfo.cellY = c.getInt(cellYIndex);
2096 folderInfo.spanX = 1;
2097 folderInfo.spanY = 1;
2098 // check & update map of what's occupied
2099 if (!checkItemPlacement(occupied, folderInfo)) {
2100 itemsToRemove.add(id);
2101 break;
2102 }
2103 switch (container) {
2104 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
2105 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2106 sBgWorkspaceItems.add(folderInfo);
2107 break;
2108 }
2109 if (restored) {
2110 // no special handling required for restored folders
2111 restoredRows.add(id);
2112 }
2113 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2114 sBgFolders.put(folderInfo.id, folderInfo);
2115 break;
2116 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
2117 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET :
2118 // Read all Launcher-specific widget details
2119 boolean customWidget = itemType == LauncherSettings.Favorites.ITEM_TY🔵
2120 int appWidgetId = c.getInt(appWidgetIdIndex);
2121 String savedProvider = c.getString(appWidgetProviderIndex);
2122 id = c.getLong(idIndex);
2123 final ComponentName component = ComponentName.unflattenFromString(sav🔵
2124 final int restoreStatus = c.getInt(restoredIndex);
2125 final boolean isIdValid = (restoreStatus & LauncherAppWidgetInfo.FLAG🔵
2126 final boolean wasProviderReady = (restoreStatus & LauncherAppWidgetIn🔵
2127 final LauncherAppWidgetProviderInfo provider = LauncherModel.getProvi🔵
2128 final boolean isProviderReady = isValidProvider(provider);
2129 if ((((!isSafeMode) && (!customWidget)) && wasProviderReady) && (!isP🔵
2130 String log = ((("Deleting widget that isn't installed anymore: " 🔵
2131 Log.e(TAG, log);
2132 Launcher.addDumpLog(TAG, log, false);
2133 itemsToRemove.add(id);
2134 } else {
2135 if (isProviderReady) {
2136 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, provid🔵
2137 if (!customWidget) {
2138 int[] minSpan = Launcher.getMinSpanForWidget(context, pro🔵
2139 appWidgetInfo.minSpanX = minSpan[0];
2140 appWidgetInfo.minSpanY = minSpan[1];
2141 }
2142 int status = restoreStatus;
2143 if (!wasProviderReady) {
2144 // If provider was not previously ready, update the
2145 // status and UI flag.
2146 // Id would be valid only if the widget restore broadcast🔵
2147 if (isIdValid) {
2148 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2149 } else {
2150 status &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_RE🔵
2151 }
2152 }
2153 appWidgetInfo.restoreStatus = status;
2154 } else {
2155 Log.v(TAG, (((("Widget restore pending id=" + id) + " appWidg🔵
2156 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, compon🔵
2157 appWidgetInfo.restoreStatus = restoreStatus;
2158 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_START🔵
2159 // Restore has started once.
2160 } else if (installingPkgs.contains(component.getPackageName()🔵
2161 // App restore has started. Update the flag
2162 appWidgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG🔵
2163 } else if (REMOVE_UNRESTORED_ICONS && (!isSafeMode)) {
2164 Launcher.addDumpLog(TAG, "Unrestored widget removed: " + 🔵
2165 itemsToRemove.add(id);
2166 continue;
2167 }
2168 }
2169 appWidgetInfo.id = id;
2170 appWidgetInfo.screenId = c.getInt(screenIndex);
2171 appWidgetInfo.cellX = c.getInt(cellXIndex);
2172 appWidgetInfo.cellY = c.getInt(cellYIndex);
2173 appWidgetInfo.spanX = c.getInt(spanXIndex);
2174 appWidgetInfo.spanY = c.getInt(spanYIndex);
2175 if (!customWidget) {
2176 int[] minSpan = Launcher.getMinSpanForWidget(context, provide🔵
2177 appWidgetInfo.minSpanX = minSpan[0];
2178 appWidgetInfo.minSpanY = minSpan[1];
2179 }
2180 container = c.getInt(containerIndex);
2181 if ((container != LauncherSettings.Favorites.CONTAINER_DESKTOP) &🔵
2182 Log.e(TAG, "Widget found where container != " + "CONTAINER_DE🔵
2183 continue;
2184 }
2185 appWidgetInfo.container = c.getInt(containerIndex);
2186 // check & update map of what's occupied
2187 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2188 itemsToRemove.add(id);
2189 break;
2190 }
2191 if (!customWidget) {
2192 String providerName = appWidgetInfo.providerName.flattenToStr🔵
2193 if ((!providerName.equals(savedProvider)) || (appWidgetInfo.r🔵
2194 ContentValues values = new ContentValues();
2195 values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,🔵
2196 values.put(LauncherSettings.Favorites.RESTORED, appWidget🔵
2197 updateItem(id, values);
2198 }
2199 }
2200 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2201 sBgAppWidgets.add(appWidgetInfo);
2202 }
2203 break;
2204 }
2205 } catch (java.lang.Exception e) {
2206 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2207 }
2208 }
2209 } finally {
2210 if (c != null) {
2211 c.close();
2212 }
2213 }
2214 // Break early if we've stopped loading
2215 if (mStopped) {
2216 clearSBgDataStructures();
2217 return;
2218 }
2219 if (itemsToRemove.size() > 0) {
2220 ContentProviderClient client = contentResolver.acquireContentProviderClient(contentUr🔵
2221 // Remove dead items
2222 for (long id : itemsToRemove) {
2223 if (DEBUG_LOADERS) {
2224 Log.d(TAG, "Removed id = " + id);
2225 }
2226 // Don't notify content observers
2227 try {
2228 client.delete(LauncherSettings.Favorites.getContentUri(id, false), null, null🔵
2229 } catch (RemoteException e) {
2230 Log.w(TAG, "Could not remove id = " + id);
2231 }
2232 }
2233 }
2234 if (restoredRows.size() > 0) {
2235 ContentProviderClient updater = contentResolver.acquireContentProviderClient(contentU🔵
2236 // Update restored items that no longer require special handling
2237 try {
2238 StringBuilder selectionBuilder = new StringBuilder();
2239 selectionBuilder.append(LauncherSettings.Favorites._ID);
2240 selectionBuilder.append(" IN (");
2241 selectionBuilder.append(TextUtils.join(", ", restoredRows));
2242 selectionBuilder.append(")");
2243 ContentValues values = new ContentValues();
2244 values.put(LauncherSettings.Favorites.RESTORED, 0);
2245 updater.update(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values, se🔵
2246 } catch (RemoteException e) {
2247 Log.w(TAG, "Could not update restored rows");
2248 }
2249 }
2250 if ((!isSdCardReady) && (!sPendingPackages.isEmpty())) {
2251 context.registerReceiver(new AppsAvailabilityCheck(), new IntentFilter(StartupReceive🔵
2252 }
2253 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2254 // Log to disk
2255 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " + TextUtils.join(", ", sBgW🔵
2256 // Remove any empty screens
2257 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2258 for (ItemInfo item : sBgItemsIdMap.values()) {
2259 long screenId = item.screenId;
2260 if ((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && unusedScreens🔵
2261 unusedScreens.remove(screenId);
2262 }
2263 }
2264 // If there are any empty screens remove them, and update.
2265 if (unusedScreens.size() != 0) {
2266 // Log to disk
2267 Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " + TextUtils.j🔵
2268 sBgWorkspaceScreens.removeAll(unusedScreens);
2269 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2270 }
2271 if (DEBUG_LOADERS) {
2272 Log.d(TAG, ("loaded workspace in " + (SystemClock.uptimeMillis() - t)) + "ms");
2273 Log.d(TAG, "workspace layout: ");
2274 int nScreens = occupied.size();
2275 for (int y = 0; y < countY; y++) {
2276 String line = "";
2277 Iterator<Long> iter = occupied.keySet().iterator();
2278 while (iter.hasNext()) {
2279 long screenId = iter.next();
2280 if (screenId > 0) {
2281 line += " | ";
2282 }
2283 for (int x = 0; x < countX; x++) {
2284 ItemInfo[][] screen = occupied.get(screenId);
2285 if ((x < screen.length) && (y < screen[x].length)) {
2286 line += (screen[x][y] != null) ? "#" : ".";
2287 } else {
2288 line += "!";
2289 }
2290 }
2291 }
2292 Log.d(TAG, ("[ " + line) + " ]");
2293 }
2294 }
2295 }
2296 }
2297
2298 /**
2299 * Partially updates the item without any notification. Must be called on the worker thread.
2300 */
2301 private void updateItem(long itemId, ContentValues update) {
2302 mContext.getContentResolver().update(
2303 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
2304 update,
2305 BaseColumns._ID + "= ?",
2306 new String[]{Long.toString(itemId)});
2307 }
2308
2309 /** Filters the set of items who are directly or indirectly (via another container) on the
2310 * specified screen. */
2311 private void filterCurrentWorkspaceItems(long currentScreenId,
2312 ArrayList<ItemInfo> allWorkspaceItems,
2313 ArrayList<ItemInfo> currentScreenItems,
2314 ArrayList<ItemInfo> otherScreenItems) {
2315 // Purge any null ItemInfos
2316 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2317 while (iter.hasNext()) {
2318 ItemInfo i = iter.next();
2319 if (i == null) {
2320 iter.remove();
2321 }
2322 }
2323
2324 // Order the set of items by their containers first, this allows use to walk through the
2325 // list sequentially, build up a list of containers that are in the specified screen,
2326 // as well as all items in those containers.
2327 Set<Long> itemsOnScreen = new HashSet<Long>();
2328 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2329 @Override
2330 public int compare(ItemInfo lhs, ItemInfo rhs) {
2331 return (int) (lhs.container - rhs.container);
2332 }
2333 });
2334 for (ItemInfo info : allWorkspaceItems) {
2335 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2336 if (info.screenId == currentScreenId) {
2337 currentScreenItems.add(info);
2338 itemsOnScreen.add(info.id);
2339 } else {
2340 otherScreenItems.add(info);
2341 }
2342 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2343 currentScreenItems.add(info);
2344 itemsOnScreen.add(info.id);
2345 } else {
2346 if (itemsOnScreen.contains(info.container)) {
2347 currentScreenItems.add(info);
2348 itemsOnScreen.add(info.id);
2349 } else {
2350 otherScreenItems.add(info);
2351 }
2352 }
2353 }
2354 }
2355
2356 /** Filters the set of widgets which are on the specified screen. */
2357 private void filterCurrentAppWidgets(long currentScreenId,
2358 ArrayList<LauncherAppWidgetInfo> appWidgets,
2359 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2360 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2361
2362 for (LauncherAppWidgetInfo widget : appWidgets) {
2363 if (widget == null) continue;
2364 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2365 widget.screenId == currentScreenId) {
2366 currentScreenWidgets.add(widget);
2367 } else {
2368 otherScreenWidgets.add(widget);
2369 }
2370 }
2371 }
2372
2373 /** Filters the set of folders which are on the specified screen. */
2374 private void filterCurrentFolders(long currentScreenId,
2375 HashMap<Long, ItemInfo> itemsIdMap,
2376 HashMap<Long, FolderInfo> folders,
2377 HashMap<Long, FolderInfo> currentScreenFolders,
2378 HashMap<Long, FolderInfo> otherScreenFolders) {
2379
2380 for (long id : folders.keySet()) {
2381 ItemInfo info = itemsIdMap.get(id);
2382 FolderInfo folder = folders.get(id);
2383 if (info == null || folder == null) continue;
2384 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2385 info.screenId == currentScreenId) {
2386 currentScreenFolders.put(id, folder);
2387 } else {
2388 otherScreenFolders.put(id, folder);
2389 }
2390 }
2391 }
2392
2393 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2394 * right) */
2395 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2396 final LauncherAppState app = LauncherAppState.getInstance();
2397 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2398 // XXX: review this
2399 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2400 @Override
2401 public int compare(ItemInfo lhs, ItemInfo rhs) {
2402 int cellCountX = (int) grid.numColumns;
2403 int cellCountY = (int) grid.numRows;
2404 int screenOffset = cellCountX * cellCountY;
2405 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2406 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2407 lhs.cellY * cellCountX + lhs.cellX);
2408 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2409 rhs.cellY * cellCountX + rhs.cellX);
2410 return (int) (lr - rr);
2411 }
2412 });
2413 }
2414
2415 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2416 final ArrayList<Long> orderedScreens) {
2417 final Runnable r = new Runnable() {
2418 @Override
2419 public void run() {
2420 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2421 if (callbacks != null) {
2422 callbacks.bindScreens(orderedScreens);
2423 }
2424 }
2425 };
2426 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2427 }
2428
2429 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2430 final ArrayList<ItemInfo> workspaceItems,
2431 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2432 final HashMap<Long, FolderInfo> folders,
2433 ArrayList<Runnable> deferredBindRunnables) {
2434
2435 final boolean postOnMainThread = (deferredBindRunnables != null);
2436
2437 // Bind the workspace items
2438 int N = workspaceItems.size();
2439 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2440 final int start = i;
2441 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2442 final Runnable r = new Runnable() {
2443 @Override
2444 public void run() {
2445 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2446 if (callbacks != null) {
2447 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2448 false);
2449 }
2450 }
2451 };
2452 if (postOnMainThread) {
2453 synchronized (deferredBindRunnables) {
2454 deferredBindRunnables.add(r);
2455 }
2456 } else {
2457 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2458 }
2459 }
2460
2461 // Bind the folders
2462 if (!folders.isEmpty()) {
2463 final Runnable r = new Runnable() {
2464 public void run() {
2465 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2466 if (callbacks != null) {
2467 callbacks.bindFolders(folders);
2468 }
2469 }
2470 };
2471 if (postOnMainThread) {
2472 synchronized (deferredBindRunnables) {
2473 deferredBindRunnables.add(r);
2474 }
2475 } else {
2476 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2477 }
2478 }
2479
2480 // Bind the widgets, one at a time
2481 N = appWidgets.size();
2482 for (int i = 0; i < N; i++) {
2483 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2484 final Runnable r = new Runnable() {
2485 public void run() {
2486 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2487 if (callbacks != null) {
2488 callbacks.bindAppWidget(widget);
2489 }
2490 }
2491 };
2492 if (postOnMainThread) {
2493 deferredBindRunnables.add(r);
2494 } else {
2495 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2496 }
2497 }
2498 }
2499
2500 /**
2501 * Binds all loaded data to actual views on the main thread.
2502 */
2503 private void bindWorkspace(int synchronizeBindPage) {
2504 final long t = SystemClock.uptimeMillis();
2505 Runnable r;
2506
2507 // Don't use these two variables in any of the callback runnables.
2508 // Otherwise we hold a reference to them.
2509 final Callbacks oldCallbacks = mCallbacks.get();
2510 if (oldCallbacks == null) {
2511 // This launcher has exited and nobody bothered to tell us. Just bail.
2512 Log.w(TAG, "LoaderTask running with no launcher");
2513 return;
2514 }
2515
2516 // Save a copy of all the bg-thread collections
2517 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2518 ArrayList<LauncherAppWidgetInfo> appWidgets =
2519 new ArrayList<LauncherAppWidgetInfo>();
2520 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
2521 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
2522 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2523 synchronized (sBgLock) {
2524 workspaceItems.addAll(sBgWorkspaceItems);
2525 appWidgets.addAll(sBgAppWidgets);
2526 folders.putAll(sBgFolders);
2527 itemsIdMap.putAll(sBgItemsIdMap);
2528 orderedScreenIds.addAll(sBgWorkspaceScreens);
2529 }
2530
2531 final boolean isLoadingSynchronously =
2532 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2533 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2534 oldCallbacks.getCurrentWorkspaceScreen();
2535 if (currScreen >= orderedScreenIds.size()) {
2536 // There may be no workspace screens (just hotseat items and an empty page).
2537 currScreen = PagedView.INVALID_RESTORE_PAGE;
2538 }
2539 final int currentScreen = currScreen;
2540 final long currentScreenId = currentScreen < 0
2541 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2542
2543 // Load all the items that are on the current page first (and in the process, unbind
2544 // all the existing workspace items before we call startBinding() below.
2545 unbindWorkspaceItemsOnMainThread();
2546
2547 // Separate the items that are on the current screen, and all the other remaining items
2548 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2549 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2550 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2551 new ArrayList<LauncherAppWidgetInfo>();
2552 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2553 new ArrayList<LauncherAppWidgetInfo>();
2554 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
2555 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
2556
2557 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2558 otherWorkspaceItems);
2559 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2560 otherAppWidgets);
2561 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2562 otherFolders);
2563 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2564 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2565
2566 // Tell the workspace that we're about to start binding items
2567 r = new Runnable() {
2568 public void run() {
2569 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2570 if (callbacks != null) {
2571 callbacks.startBinding();
2572 }
2573 }
2574 };
2575 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2576
2577 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2578
2579 // Load items on the current page
2580 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2581 currentFolders, null);
2582 if (isLoadingSynchronously) {
2583 r = new Runnable() {
2584 public void run() {
2585 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2586 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2587 callbacks.onPageBoundSynchronously(currentScreen);
2588 }
2589 }
2590 };
2591 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2592 }
2593
2594 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2595 // work until after the first render)
2596 synchronized (mDeferredBindRunnables) {
2597 mDeferredBindRunnables.clear();
2598 }
2599 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2600 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2601
2602 // Tell the workspace that we're done binding items
2603 r = new Runnable() {
2604 public void run() {
2605 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2606 if (callbacks != null) {
2607 callbacks.finishBindingItems();
2608 }
2609
2610 // If we're profiling, ensure this is the last thing in the queue.
2611 if (DEBUG_LOADERS) {
2612 Log.d(TAG, "bound workspace in "
2613 + (SystemClock.uptimeMillis()-t) + "ms");
2614 }
2615
2616 mIsLoadingAndBindingWorkspace = false;
2617 }
2618 };
2619 if (isLoadingSynchronously) {
2620 synchronized (mDeferredBindRunnables) {
2621 mDeferredBindRunnables.add(r);
2622 }
2623 } else {
2624 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2625 }
2626 }
2627
2628 private void loadAndBindAllApps() {
2629 if (DEBUG_LOADERS) {
2630 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2631 }
2632 if (!mAllAppsLoaded) {
2633 loadAllApps();
2634 synchronized (LoaderTask.this) {
2635 if (mStopped) {
2636 return;
2637 }
2638 mAllAppsLoaded = true;
2639 }
2640 } else {
2641 onlyBindAllApps();
2642 }
2643 }
2644
2645 private void onlyBindAllApps() {
2646 final Callbacks oldCallbacks = mCallbacks.get();
2647 if (oldCallbacks == null) {
2648 // This launcher has exited and nobody bothered to tell us. Just bail.
2649 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2650 return;
2651 }
2652
2653 // shallow copy
2654 @SuppressWarnings("unchecked")
2655 final ArrayList<AppInfo> list
2656 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2657 Runnable r = new Runnable() {
2658 public void run() {
2659 final long t = SystemClock.uptimeMillis();
2660 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2661 if (callbacks != null) {
2662 callbacks.bindAllApplications(list);
2663 }
2664 if (DEBUG_LOADERS) {
2665 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2666 + (SystemClock.uptimeMillis()-t) + "ms");
2667 }
2668 }
2669 };
2670 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2671 if (isRunningOnMainThread) {
2672 r.run();
2673 } else {
2674 mHandler.post(r);
2675 }
2676 }
2677
2678 private void loadAllApps() {
2679 final long loadTime = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
2680 final Callbacks oldCallbacks = mCallbacks.get();
2681 if (oldCallbacks == null) {
2682 // This launcher has exited and nobody bothered to tell us. Just bail.
2683 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2684 return;
2685 }
2686 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
2687 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2688 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2689 // Clear the list of apps
2690 mBgAllAppsList.clear();
2691 SharedPreferences prefs = mContext.getSharedPreferences(LauncherAppState.getSharedPreferences🔵
2692 for (UserHandleCompat user : profiles) {
2693 // Query for the set of apps
2694 final long qiaTime = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
2695 List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2696 if (DEBUG_LOADERS) {
2697 Log.d(TAG, (("getActivityList took " + (SystemClock.uptimeMillis() - qiaTime)) + "ms 🔵
2698 Log.d(TAG, (("getActivityList got " + apps.size()) + " apps for user ") + user);
2699 }
2700 // Fail if we don't have any apps
2701 // TODO: Fix this. Only fail for the current user.
2702 if ((apps == null) || apps.isEmpty()) {
2703 return;
2704 }
2705 // Update icon cache
2706 HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
2707 // If any package icon has changed (app was updated while launcher was dead),
2708 // update the corresponding shortcuts.
2709 if (!updatedPackages.isEmpty()) {
2710 final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
2711 synchronized(sBgLock) {
2712 for (ItemInfo info : sBgItemsIdMap.values()) {
2713 if (((info instanceof ShortcutInfo) && user.equals(info.user)) && (info.itemT🔵
2714 ShortcutInfo si = ((ShortcutInfo) (info));
2715 ComponentName cn = si.getTargetComponent();
2716 if ((cn != null) && updatedPackages.contains(cn.getPackageName())) {
2717 si.updateIcon(mIconCache);
2718 updates.add(si);
2719 }
2720 }
2721 }
2722 }
2723 if (!updates.isEmpty()) {
2724 final UserHandleCompat userFinal = user;
2725 mHandler.post(new Runnable() {
2726 public void run() {
2727 Callbacks cb = getCallback();
2728 if (cb != null) {
2729 cb.bindShortcutsChanged(updates, new ArrayList<ShortcutInfo>(), userF🔵
2730 }
2731 }
2732 });
2733 }
2734 }
2735 // Create the ApplicationInfos
2736 for (int i = 0; i < apps.size(); i++) {
2737 LauncherActivityInfoCompat app = apps.get(i);
2738 // This builds the icon bitmaps.
2739 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2740 }
2741 if (ADD_MANAGED_PROFILE_SHORTCUTS && (!user.equals(UserHandleCompat.myUserHandle()))) {
2742 // Add shortcuts for packages which were installed while launcher was dead.
2743 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX + mUserManager.getSerialNumbe🔵
2744 Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET🔵
2745 HashSet<String> newPackageSet = new HashSet<String>();
2746 for (LauncherActivityInfoCompat info : apps) {
2747 String packageName = info.getComponentName().getPackageName();
2748 if ((!packagesAdded.contains(packageName)) && (!newPackageSet.contains(packageNam🔵
2749 InstallShortcutReceiver.queueInstallShortcut(info, mContext);
2750 }
2751 newPackageSet.add(packageName);
2752 }
2753 prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
2754 }
2755 }
2756 // Huh? Shouldn't this be inside the Runnable below?
2757 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2758 mBgAllAppsList.added = new ArrayList<AppInfo>();
2759 // Post callback on main thread
2760 mHandler.post(new Runnable() {
2761 public void run() {
2762 final long bindTime = SystemClock.uptimeMillis();
2763 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2764 if (callbacks != null) {
2765 callbacks.bindAllApplications(added);
2766 if (DEBUG_LOADERS) {
2767 Log.d(TAG, ((("bound " + added.size()) + " apps in ") + (SystemClock.uptimeMi🔵
2768 }
2769 } else {
2770 Log.i(TAG, "not binding apps: no Launcher activity");
2771 }
2772 }
2773 });
2774 if (DEBUG_LOADERS) {
2775 Log.d(TAG, ("Icons processed in " + (SystemClock.uptimeMillis() - loadTime)) + "ms");
2776 }
2777 }
2778
2779 public void dumpState() {
2780 synchronized (sBgLock) {
2781 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2782 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
2783 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2784 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2785 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2786 }
2787 }
2788 }
2789
2790 void enqueuePackageUpdated(PackageUpdatedTask task) {
2791 sWorker.post(task);
2792 }
2793
2794 private class AppsAvailabilityCheck extends BroadcastReceiver {
2795 @Override
2796 public void onReceive(Context context, Intent intent) {
2797 synchronized(sBgLock) {
2798 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mApp.getContext())🔵
2799 final PackageManager manager = context.getPackageManager();
2800 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2801 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2802 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2803 UserHandleCompat user = entry.getKey();
2804 packagesRemoved.clear();
2805 packagesUnavailable.clear();
2806 for (String pkg : entry.getValue()) {
2807 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2808 boolean packageOnSdcard = launcherApps.isAppEnabled(manager, pkg, PackageMana🔵
2809 if (packageOnSdcard) {
2810 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2811 packagesUnavailable.add(pkg);
2812 } else {
2813 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2814 packagesRemoved.add(pkg);
2815 }
2816 }
2817 }
2818 if (!packagesRemoved.isEmpty()) {
2819 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE, packag🔵
2820 }
2821 if (!packagesUnavailable.isEmpty()) {
2822 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE, p🔵
2823 }
2824 }
2825 sPendingPackages.clear();
2826 }
2827 }
2828 }
2829
2830 private class PackageUpdatedTask implements Runnable {
2831 int mOp;
2832
2833 String[] mPackages;
2834
2835 UserHandleCompat mUser;
2836
2837 public static final int OP_NONE = 0;
2838
2839 public static final int OP_ADD = 1;
2840
2841 public static final int OP_UPDATE = 2;
2842
2843 // uninstlled
2844 public static final int OP_REMOVE = 3; // uninstlled
2845
2846 // external media unmounted
2847 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2848
2849 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2850 mOp = op;
2851 mPackages = packages;
2852 mUser = user;
2853 }
2854
2855 public void run() {
2856 final Context context = mApp.getContext();
2857 final String[] packages = mPackages;
2858 final int N = packages.length;
2859 switch (mOp) {
2860 case OP_ADD :
2861 for (int i = 0; i < N; i++) {
2862 if (DEBUG_LOADERS) {
2863 Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2864 }
2865 mIconCache.updateIconsForPkg(packages[i], mUser);
2866 mBgAllAppsList.addPackage(context, packages[i], mUser);
2867 }
2868 // Auto add shortcuts for added packages.
2869 if (ADD_MANAGED_PROFILE_SHORTCUTS && (!UserHandleCompat.myUserHandle().equals(mUser))🔵
2870 SharedPreferences prefs = context.getSharedPreferences(LauncherAppState.getShared🔵
2871 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX + mUserManager.getSerialN🔵
2872 Set<String> shortcutSet = new HashSet<String>(prefs.getStringSet(shortcutsSetKey,🔵
2873 for (int i = 0; i < N; i++) {
2874 if (!shortcutSet.contains(packages[i])) {
2875 shortcutSet.add(packages[i]);
2876 List<LauncherActivityInfoCompat> activities = mLauncherApps.getActivityLi🔵
2877 if ((activities != null) && (!activities.isEmpty())) {
2878 InstallShortcutReceiver.queueInstallShortcut(activities.get(0), conte🔵
2879 }
2880 }
2881 }
2882 prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
2883 }
2884 break;
2885 case OP_UPDATE :
2886 for (int i = 0; i < N; i++) {
2887 if (DEBUG_LOADERS) {
2888 Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2889 }
2890 mIconCache.updateIconsForPkg(packages[i], mUser);
2891 mBgAllAppsList.updatePackage(context, packages[i], mUser);
2892 WidgetPreviewLoader.removePackageFromDb(mApp.getWidgetPreviewCacheDb(), packages[🔵
2893 }
2894 break;
2895 case OP_REMOVE :
2896 // Remove the packageName for the set of auto-installed shortcuts. This
2897 // will ensure that the shortcut when the app is installed again.
2898 if (ADD_MANAGED_PROFILE_SHORTCUTS && (!UserHandleCompat.myUserHandle().equals(mUser))🔵
2899 SharedPreferences prefs = context.getSharedPreferences(LauncherAppState.getShared🔵
2900 String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX + mUserManager.getSerialN🔵
2901 HashSet<String> shortcutSet = new HashSet<String>(prefs.getStringSet(shortcutsSet🔵
2902 shortcutSet.removeAll(Arrays.asList(mPackages));
2903 prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
2904 }
2905 for (int i = 0; i < N; i++) {
2906 if (DEBUG_LOADERS) {
2907 Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2908 }
2909 mIconCache.removeIconsForPkg(packages[i], mUser);
2910 }
2911 // Fall through
2912 case OP_UNAVAILABLE :
2913 for (int i = 0; i < N; i++) {
2914 if (DEBUG_LOADERS) {
2915 Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2916 }
2917 mBgAllAppsList.removePackage(packages[i], mUser);
2918 WidgetPreviewLoader.removePackageFromDb(mApp.getWidgetPreviewCacheDb(), packages[🔵
2919 }
2920 break;
2921 }
2922 ArrayList<AppInfo> added = null;
2923 ArrayList<AppInfo> modified = null;
2924 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
2925 if (mBgAllAppsList.added.size() > 0) {
2926 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
2927 mBgAllAppsList.added.clear();
2928 }
2929 if (mBgAllAppsList.modified.size() > 0) {
2930 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
2931 mBgAllAppsList.modified.clear();
2932 }
2933 if (mBgAllAppsList.removed.size() > 0) {
2934 removedApps.addAll(mBgAllAppsList.removed);
2935 mBgAllAppsList.removed.clear();
2936 }
2937 final Callbacks callbacks = getCallback();
2938 if (callbacks == null) {
2939 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
2940 return;
2941 }
2942 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<ComponentName, AppInfo🔵
2943 if (added != null) {
2944 addAppsToAllApps(context, added);
2945 for (AppInfo ai : added) {
2946 addedOrUpdatedApps.put(ai.componentName, ai);
2947 }
2948 }
2949 if (modified != null) {
2950 final ArrayList<AppInfo> modifiedFinal = modified;
2951 for (AppInfo ai : modified) {
2952 addedOrUpdatedApps.put(ai.componentName, ai);
2953 }
2954 mHandler.post(new Runnable() {
2955 public void run() {
2956 Callbacks cb = getCallback();
2957 if ((callbacks == cb) && (cb != null)) {
2958 callbacks.bindAppsUpdated(modifiedFinal);
2959 }
2960 }
2961 });
2962 }
2963 // Update shortcut infos
2964 if ((mOp == OP_ADD) || (mOp == OP_UPDATE)) {
2965 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
2966 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
2967 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
2968 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
2969 synchronized(sBgLock) {
2970 for (ItemInfo info : sBgItemsIdMap.values()) {
2971 if ((info instanceof ShortcutInfo) && mUser.equals(info.user)) {
2972 ShortcutInfo si = ((ShortcutInfo) (info));
2973 boolean infoUpdated = false;
2974 boolean shortcutUpdated = false;
2975 // Update shortcuts which use iconResource.
2976 if ((si.iconResource != null) && packageSet.contains(si.iconResource.packageN🔵
2977 Bitmap icon = Utilities.createIconBitmap(si.iconResource.packageName, si.🔵
2978 if (icon != null) {
2979 si.setIcon(icon);
2980 si.usingFallbackIcon = false;
2981 infoUpdated = true;
2982 }
2983 }
2984 ComponentName cn = si.getTargetComponent();
2985 if ((cn != null) && packageSet.contains(cn.getPackageName())) {
2986 AppInfo appInfo = addedOrUpdatedApps.get(cn);
2987 if (si.isPromise()) {
2988 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
2989 // Auto install icon
2990 PackageManager pm = context.getPackageManager();
2991 ResolveInfo matched = pm.resolveActivity(new Intent(Intent.ACTION🔵
2992 if (matched == null) {
2993 // Try to find the best match activity.
2994 Intent intent = pm.getLaunchIntentForPackage(cn.getPackageNam🔵
2995 if (intent != null) {
2996 cn = intent.getComponent();
2997 appInfo = addedOrUpdatedApps.get(cn);
2998 }
2999 if ((intent == null) || (appInfo == null)) {
3000 removedShortcuts.add(si);
3001 continue;
3002 }
3003 si.promisedIntent = intent;
3004 }
3005 }
3006 // Restore the shortcut.
3007 si.intent = si.promisedIntent;
3008 si.promisedIntent = null;
3009 si.status &= ((~ShortcutInfo.FLAG_RESTORED_ICON) & (~ShortcutInfo.FLA🔵
3010 infoUpdated = true;
3011 si.updateIcon(mIconCache);
3012 }
3013 if (((appInfo != null) && Intent.ACTION_MAIN.equals(si.intent.getAction()🔵
3014 si.updateIcon(mIconCache);
3015 si.title = appInfo.title.toString();
3016 si.contentDescription = appInfo.contentDescription;
3017 infoUpdated = true;
3018 }
3019 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3020 // Since package was just updated, the target must be available now.
3021 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3022 shortcutUpdated = true;
3023 }
3024 }
3025 if (infoUpdated || shortcutUpdated) {
3026 updatedShortcuts.add(si);
3027 }
3028 if (infoUpdated) {
3029 updateItemInDatabase(context, si);
3030 }
3031 } else if (info instanceof LauncherAppWidgetInfo) {
3032 LauncherAppWidgetInfo widgetInfo = ((LauncherAppWidgetInfo) (info));
3033 if ((mUser.equals(widgetInfo.user) && widgetInfo.hasRestoreFlag(LauncherAppWi🔵
3034 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3035 widgets.add(widgetInfo);
3036 updateItemInDatabase(context, widgetInfo);
3037 }
3038 }
3039 }
3040 }
3041 if ((!updatedShortcuts.isEmpty()) || (!removedShortcuts.isEmpty())) {
3042 mHandler.post(new Runnable() {
3043 public void run() {
3044 Callbacks cb = getCallback();
3045 if ((callbacks == cb) && (cb != null)) {
3046 callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, mUser)🔵
3047 }
3048 }
3049 });
3050 if (!removedShortcuts.isEmpty()) {
3051 deleteItemsFromDatabase(context, removedShortcuts);
3052 }
3053 }
3054 if (!widgets.isEmpty()) {
3055 mHandler.post(new Runnable() {
3056 public void run() {
3057 Callbacks cb = getCallback();
3058 if ((callbacks == cb) && (cb != null)) {
3059 callbacks.bindWidgetsRestored(widgets);
3060 }
3061 }
3062 });
3063 }
3064 }
3065 final ArrayList<String> removedPackageNames = new ArrayList<String>();
3066 if ((mOp == OP_REMOVE) || (mOp == OP_UNAVAILABLE)) {
3067 // Mark all packages in the broadcast to be removed
3068 removedPackageNames.addAll(Arrays.asList(packages));
3069 } else if (mOp == OP_UPDATE) {
3070 // Mark disabled packages in the broadcast to be removed
3071 for (int i = 0; i < N; i++) {
3072 if (isPackageDisabled(context, packages[i], mUser)) {
3073 removedPackageNames.add(packages[i]);
3074 }
3075 }
3076 }
3077 if ((!removedPackageNames.isEmpty()) || (!removedApps.isEmpty())) {
3078 final int removeReason;
3079 if (mOp == OP_UNAVAILABLE) {
3080 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3081 } else {
3082 // Remove all the components associated with this package
3083 for (String pn : removedPackageNames) {
3084 deletePackageFromDatabase(context, pn, mUser);
3085 }
3086 // Remove all the specific components
3087 for (AppInfo a : removedApps) {
3088 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3089 deleteItemsFromDatabase(context, infos);
3090 }
3091 removeReason = 0;
3092 }
3093 // Remove any queued items from the install queue
3094 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3095 // Call the components-removed callback
3096 mHandler.post(new Runnable() {
3097 public void run() {
3098 Callbacks cb = getCallback();
3099 if ((callbacks == cb) && (cb != null)) {
3100 callbacks.bindComponentsRemoved(removedPackageNames, removedApps, mUser, remo🔵
3101 }
3102 }
3103 });
3104 }
3105 final ArrayList<Object> widgetsAndShortcuts = getSortedWidgetsAndShortcuts(context);
3106 mHandler.post(new Runnable() {
3107 @Override
3108 public void run() {
3109 Callbacks cb = getCallback();
3110 if ((callbacks == cb) && (cb != null)) {
3111 callbacks.bindPackagesUpdated(widgetsAndShortcuts);
3112 }
3113 }
3114 });
3115 // Write all the logs to disk
3116 mHandler.post(new Runnable() {
3117 public void run() {
3118 Callbacks cb = getCallback();
3119 if ((callbacks == cb) && (cb != null)) {
3120 callbacks.dumpLogsToLocalData();
3121 }
3122 }
3123 });
3124 }
3125 }
3126
3127 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context) {
3128 synchronized (sBgLock) {
3129 if (sBgWidgetProviders != null && !sWidgetProvidersDirty) {
3130 return new ArrayList<LauncherAppWidgetProviderInfo>(sBgWidgetProviders.values());
3131 }
3132 sBgWidgetProviders = new HashMap<ComponentName, LauncherAppWidgetProviderInfo>();
3133 List<AppWidgetProviderInfo> widgets =
3134 AppWidgetManagerCompat.getInstance(context).getAllProviders();
3135 LauncherAppWidgetProviderInfo info;
3136 for (AppWidgetProviderInfo pInfo : widgets) {
3137 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3138 sBgWidgetProviders.put(info.provider, info);
3139 }
3140
3141 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3142 for (CustomAppWidget widget : customWidgets) {
3143 info = new LauncherAppWidgetProviderInfo(context, widget);
3144 sBgWidgetProviders.put(info.provider, info);
3145 }
3146 sWidgetProvidersDirty = false;
3147 return new ArrayList<LauncherAppWidgetProviderInfo>(sBgWidgetProviders.values());
3148 }
3149 }
3150
3151 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name) {
3152 synchronized (sBgLock) {
3153 if (sBgWidgetProviders == null) {
3154 getWidgetProviders(ctx);
3155 }
3156 return sBgWidgetProviders.get(name);
3157 }
3158 }
3159
3160 // Returns a list of ResolveInfos/AppWindowInfos in sorted order
3161 public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
3162 PackageManager packageManager = context.getPackageManager();
3163 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3164 widgetsAndShortcuts.addAll(getWidgetProviders(context));
3165 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3166 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3167 Collections.sort(widgetsAndShortcuts, new WidgetAndShortcutNameComparator(context));
3168 return widgetsAndShortcuts;
3169 }
3170
3171 private static boolean isPackageDisabled(Context context, String packageName,
3172 UserHandleCompat user) {
3173 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3174 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3175 }
3176
3177 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3178 UserHandleCompat user) {
3179 if (cn == null) {
3180 return false;
3181 }
3182 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3183 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3184 return false;
3185 }
3186 return launcherApps.isActivityEnabledForProfile(cn, user);
3187 }
3188
3189 public static boolean isValidPackage(Context context, String packageName,
3190 UserHandleCompat user) {
3191 if (packageName == null) {
3192 return false;
3193 }
3194 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3195 return launcherApps.isPackageEnabledForProfile(packageName, user);
3196 }
3197
3198 /**
3199 * Make an ShortcutInfo object for a restored application or shortcut item that points
3200 * to a package that is not yet installed on the system.
3201 */
3202 public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent, int promiseType🔵
3203 final ShortcutInfo info = new ShortcutInfo();
3204 info.user = UserHandleCompat.myUserHandle();
3205 mIconCache.getTitleAndIcon(info, intent, info.user);
3206 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3207 String title = (cursor != null) ? cursor.getString(titleIndex) : null;
3208 if (!TextUtils.isEmpty(title)) {
3209 info.title = title;
3210 }
3211 info.status = ShortcutInfo.FLAG_RESTORED_ICON;
3212 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3213 if (TextUtils.isEmpty(info.title)) {
3214 info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
3215 }
3216 info.status = ShortcutInfo.FLAG_AUTOINTALL_ICON;
3217 } else {
3218 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3219 }
3220 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title.toString(), info.user);
3221 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3222 info.promisedIntent = intent;
3223 return info;
3224 }
3225
3226 /**
3227 * Make an Intent object for a restored application or shortcut item that points
3228 * to the market page for the item.
3229 */
3230 private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3231 ComponentName componentName = intent.getComponent();
3232 return getMarketIntent(componentName.getPackageName());
3233 }
3234
3235 static Intent getMarketIntent(String packageName) {
3236 return new Intent(Intent.ACTION_VIEW)
3237 .setData(new Uri.Builder()
3238 .scheme("market")
3239 .authority("details")
3240 .appendQueryParameter("id", packageName)
3241 .build());
3242 }
3243
3244 /**
3245 * Make an ShortcutInfo object for a shortcut that is an application.
3246 *
3247 * If c is not null, then it will be used to fill in missing data like the title and icon.
3248 */
3249 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent, UserHandleCompat user, 🔵
3250 if (user == null) {
3251 Log.d(TAG, "Null user found in getShortcutInfo");
3252 return null;
3253 }
3254 ComponentName componentName = intent.getComponent();
3255 if (componentName == null) {
3256 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3257 return null;
3258 }
3259 Intent newIntent = new Intent(intent.getAction(), null);
3260 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3261 newIntent.setComponent(componentName);
3262 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3263 if ((lai == null) && (!allowMissingTarget)) {
3264 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3265 return null;
3266 }
3267 final ShortcutInfo info = new ShortcutInfo();
3268 mIconCache.getTitleAndIcon(info, componentName, lai, user, false);
3269 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && (c != null)) {
3270 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3271 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3272 }
3273 // from the db
3274 if (TextUtils.isEmpty(info.title) && (c != null)) {
3275 info.title = c.getString(titleIndex);
3276 }
3277 // fall back to the class name of the activity
3278 if (info.title == null) {
3279 info.title = componentName.getClassName();
3280 }
3281 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3282 info.user = user;
3283 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title.toString(), info.user);
3284 return info;
3285 }
3286
3287 static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
3288 ItemInfoFilter f) {
3289 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3290 for (ItemInfo i : infos) {
3291 if (i instanceof ShortcutInfo) {
3292 ShortcutInfo info = (ShortcutInfo) i;
3293 ComponentName cn = info.getTargetComponent();
3294 if (cn != null && f.filterItem(null, info, cn)) {
3295 filtered.add(info);
3296 }
3297 } else if (i instanceof FolderInfo) {
3298 FolderInfo info = (FolderInfo) i;
3299 for (ShortcutInfo s : info.contents) {
3300 ComponentName cn = s.getTargetComponent();
3301 if (cn != null && f.filterItem(info, s, cn)) {
3302 filtered.add(s);
3303 }
3304 }
3305 } else if (i instanceof LauncherAppWidgetInfo) {
3306 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3307 ComponentName cn = info.providerName;
3308 if (cn != null && f.filterItem(null, info, cn)) {
3309 filtered.add(info);
3310 }
3311 }
3312 }
3313 return new ArrayList<ItemInfo>(filtered);
3314 }
3315
3316 private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3317 final UserHandleCompat user) {
3318 ItemInfoFilter filter = new ItemInfoFilter() {
3319 @Override
3320 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3321 if (info.user == null) {
3322 return cn.equals(cname);
3323 } else {
3324 return cn.equals(cname) && info.user.equals(user);
3325 }
3326 }
3327 };
3328 return filterItemInfos(sBgItemsIdMap.values(), filter);
3329 }
3330
3331 /**
3332 * Make an ShortcutInfo object for a shortcut that isn't an application.
3333 */
3334 private ShortcutInfo getShortcutInfo(Cursor c, Context context, int iconTypeIndex, int iconPackageInd🔵
3335 Bitmap icon = null;
3336 final ShortcutInfo info = new ShortcutInfo();
3337 // Non-app shortcuts are only supported for current user.
3338 info.user = UserHandleCompat.myUserHandle();
3339 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3340 // TODO: If there's an explicit component and we can't install that, delete it.
3341 info.title = c.getString(titleIndex);
3342 int iconType = c.getInt(iconTypeIndex);
3343 switch (iconType) {
3344 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE :
3345 String packageName = c.getString(iconPackageIndex);
3346 String resourceName = c.getString(iconResourceIndex);
3347 info.customIcon = false;
3348 // the resource
3349 icon = Utilities.createIconBitmap(packageName, resourceName, mIconCache, context);
3350 // the db
3351 if (icon == null) {
3352 icon = Utilities.createIconBitmap(c, iconIndex, context);
3353 }
3354 // the fallback icon
3355 if (icon == null) {
3356 icon = mIconCache.getDefaultIcon(info.user);
3357 info.usingFallbackIcon = true;
3358 }
3359 break;
3360 case LauncherSettings.Favorites.ICON_TYPE_BITMAP :
3361 icon = Utilities.createIconBitmap(c, iconIndex, context);
3362 if (icon == null) {
3363 icon = mIconCache.getDefaultIcon(info.user);
3364 info.customIcon = false;
3365 info.usingFallbackIcon = true;
3366 } else {
3367 info.customIcon = true;
3368 }
3369 break;
3370 default :
3371 icon = mIconCache.getDefaultIcon(info.user);
3372 info.usingFallbackIcon = true;
3373 info.customIcon = false;
3374 break;
3375 }
3376 info.setIcon(icon);
3377 return info;
3378 }
3379
3380 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3381 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3382 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3383 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3384
3385 if (intent == null) {
3386 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3387 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3388 return null;
3389 }
3390
3391 Bitmap icon = null;
3392 boolean customIcon = false;
3393 ShortcutIconResource iconResource = null;
3394
3395 if (bitmap instanceof Bitmap) {
3396 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3397 customIcon = true;
3398 } else {
3399 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3400 if (extra instanceof ShortcutIconResource) {
3401 iconResource = (ShortcutIconResource) extra;
3402 icon = Utilities.createIconBitmap(iconResource.packageName,
3403 iconResource.resourceName, mIconCache, context);
3404 }
3405 }
3406
3407 final ShortcutInfo info = new ShortcutInfo();
3408
3409 // Only support intents for current user for now. Intents sent from other
3410 // users wouldn't get here without intent forwarding anyway.
3411 info.user = UserHandleCompat.myUserHandle();
3412 if (icon == null) {
3413 icon = mIconCache.getDefaultIcon(info.user);
3414 info.usingFallbackIcon = true;
3415 }
3416 info.setIcon(icon);
3417
3418 info.title = name;
3419 info.contentDescription = mUserManager.getBadgedLabelForUser(
3420 info.title.toString(), info.user);
3421 info.intent = intent;
3422 info.customIcon = customIcon;
3423 info.iconResource = iconResource;
3424
3425 return info;
3426 }
3427
3428 /**
3429 * Return an existing FolderInfo object if we have encountered this ID previously,
3430 * or make a new one.
3431 */
3432 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
3433 // See if a placeholder was created for us already
3434 FolderInfo folderInfo = folders.get(id);
3435 if (folderInfo == null) {
3436 // No placeholder -- create a new instance
3437 folderInfo = new FolderInfo();
3438 folders.put(id, folderInfo);
3439 }
3440 return folderInfo;
3441 }
3442
3443 public static final Comparator<AppInfo> getAppNameComparator() {
3444 final Collator collator = Collator.getInstance();
3445 return new Comparator<AppInfo>() {
3446 public final int compare(AppInfo a, AppInfo b) {
3447 if (a.user.equals(b.user)) {
3448 int result = collator.compare(a.title.toString().trim(),
3449 b.title.toString().trim());
3450 if (result == 0) {
3451 result = a.componentName.compareTo(b.componentName);
3452 }
3453 return result;
3454 } else {
3455 // TODO Need to figure out rules for sorting
3456 // profiles, this puts work second.
3457 return a.user.toString().compareTo(b.user.toString());
3458 }
3459 }
3460 };
3461 }
3462
3463 public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR = new Comparator<AppInfo>() {
3464 public final int compare(AppInfo a, AppInfo b) {
3465 if (a.firstInstallTime < b.firstInstallTime) {
3466 return 1;
3467 }
3468 if (a.firstInstallTime > b.firstInstallTime) {
3469 return -1;
3470 }
3471 return 0;
3472 }
3473 };
3474
3475 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
3476 if (info.activityInfo != null) {
3477 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
3478 } else {
3479 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
3480 }
3481 }
3482
3483 public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
3484 private final AppWidgetManagerCompat mManager;
3485
3486 private final PackageManager mPackageManager;
3487
3488 private final HashMap<Object, String> mLabelCache;
3489
3490 private final Collator mCollator;
3491
3492 WidgetAndShortcutNameComparator(Context context) {
3493 mManager = AppWidgetManagerCompat.getInstance(context);
3494 mPackageManager = context.getPackageManager();
3495 mLabelCache = new HashMap<Object, String>();
3496 mCollator = Collator.getInstance();
3497 }
3498
3499 public final int compare(Object a, Object b) {
3500 String labelA;
3501 String labelB;
3502 if (mLabelCache.containsKey(a)) {
3503 labelA = mLabelCache.get(a);
3504 } else {
3505 labelA = (a instanceof LauncherAppWidgetProviderInfo) ? mManager.loadLabel(((LauncherAppW🔵
3506 mLabelCache.put(a, labelA);
3507 }
3508 if (mLabelCache.containsKey(b)) {
3509 labelB = mLabelCache.get(b);
3510 } else {
3511 labelB = (b instanceof LauncherAppWidgetProviderInfo) ? mManager.loadLabel(((LauncherAppW🔵
3512 mLabelCache.put(b, labelB);
3513 }
3514 return mCollator.compare(labelA, labelB);
3515 }
3516 }
3517
3518 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3519 return (provider != null) && (provider.provider != null)
3520 && (provider.provider.getPackageName() != null);
3521 }
3522
3523 public void dumpState() {
3524 Log.d(TAG, "mCallbacks=" + mCallbacks);
3525 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3526 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3527 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3528 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3529 if (mLoaderTask != null) {
3530 mLoaderTask.dumpState();
3531 } else {
3532 Log.d(TAG, "mLoaderTask=null");
3533 }
3534 }
3535
3536 public Callbacks getCallback() {
3537 return mCallbacks != null ? mCallbacks.get() : null;
3538 }
3539 }
|